chore: 初始化 Cloudreve 源码仓库
This commit is contained in:
@@ -0,0 +1,207 @@
|
||||
package conf
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/cloudreve/Cloudreve/v4/pkg/logging"
|
||||
"github.com/cloudreve/Cloudreve/v4/pkg/util"
|
||||
"github.com/go-ini/ini"
|
||||
"github.com/go-playground/validator/v10"
|
||||
)
|
||||
|
||||
const (
|
||||
envConfOverrideKey = "CR_CONF_"
|
||||
)
|
||||
|
||||
type ConfigProvider interface {
|
||||
Database() *Database
|
||||
System() *System
|
||||
SSL() *SSL
|
||||
Unix() *Unix
|
||||
Slave() *Slave
|
||||
Redis() *Redis
|
||||
Cors() *Cors
|
||||
OptionOverwrite() map[string]any
|
||||
}
|
||||
|
||||
// NewIniConfigProvider initializes a new Ini config file provider. A default config file
|
||||
// will be created if the given path does not exist.
|
||||
func NewIniConfigProvider(configPath string, l logging.Logger) (ConfigProvider, error) {
|
||||
if configPath == "" || !util.Exists(configPath) {
|
||||
l.Info("Config file %q not found, creating a new one.", configPath)
|
||||
// 创建初始配置文件
|
||||
confContent := util.Replace(map[string]string{
|
||||
"{SessionSecret}": util.RandStringRunesCrypto(64),
|
||||
}, defaultConf)
|
||||
f, err := util.CreatNestedFile(configPath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create config file: %w", err)
|
||||
}
|
||||
|
||||
// 写入配置文件
|
||||
_, err = f.WriteString(confContent)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to write config file: %w", err)
|
||||
}
|
||||
|
||||
f.Close()
|
||||
}
|
||||
|
||||
cfg, err := ini.Load(configPath, []byte(getOverrideConfFromEnv(l)))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse config file %q: %w", configPath, err)
|
||||
}
|
||||
|
||||
provider := &iniConfigProvider{
|
||||
database: *DatabaseConfig,
|
||||
system: *SystemConfig,
|
||||
ssl: *SSLConfig,
|
||||
unix: *UnixConfig,
|
||||
slave: *SlaveConfig,
|
||||
redis: *RedisConfig,
|
||||
cors: *CORSConfig,
|
||||
optionOverwrite: make(map[string]interface{}),
|
||||
}
|
||||
|
||||
sections := map[string]interface{}{
|
||||
"Database": &provider.database,
|
||||
"System": &provider.system,
|
||||
"SSL": &provider.ssl,
|
||||
"UnixSocket": &provider.unix,
|
||||
"Redis": &provider.redis,
|
||||
"CORS": &provider.cors,
|
||||
"Slave": &provider.slave,
|
||||
}
|
||||
for sectionName, sectionStruct := range sections {
|
||||
err = mapSection(cfg, sectionName, sectionStruct)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse config section %q: %w", sectionName, err)
|
||||
}
|
||||
}
|
||||
|
||||
// 映射数据库配置覆盖
|
||||
for _, key := range cfg.Section("OptionOverwrite").Keys() {
|
||||
provider.optionOverwrite[key.Name()] = key.Value()
|
||||
}
|
||||
|
||||
return provider, nil
|
||||
}
|
||||
|
||||
type iniConfigProvider struct {
|
||||
database Database
|
||||
system System
|
||||
ssl SSL
|
||||
unix Unix
|
||||
slave Slave
|
||||
redis Redis
|
||||
cors Cors
|
||||
optionOverwrite map[string]any
|
||||
}
|
||||
|
||||
func (i *iniConfigProvider) Database() *Database {
|
||||
return &i.database
|
||||
}
|
||||
|
||||
func (i *iniConfigProvider) System() *System {
|
||||
return &i.system
|
||||
}
|
||||
|
||||
func (i *iniConfigProvider) SSL() *SSL {
|
||||
return &i.ssl
|
||||
}
|
||||
|
||||
func (i *iniConfigProvider) Unix() *Unix {
|
||||
return &i.unix
|
||||
}
|
||||
|
||||
func (i *iniConfigProvider) Slave() *Slave {
|
||||
return &i.slave
|
||||
}
|
||||
|
||||
func (i *iniConfigProvider) Redis() *Redis {
|
||||
return &i.redis
|
||||
}
|
||||
|
||||
func (i *iniConfigProvider) Cors() *Cors {
|
||||
return &i.cors
|
||||
}
|
||||
|
||||
func (i *iniConfigProvider) OptionOverwrite() map[string]any {
|
||||
return i.optionOverwrite
|
||||
}
|
||||
|
||||
const defaultConf = `[System]
|
||||
Debug = false
|
||||
Mode = master
|
||||
Listen = :5212
|
||||
SessionSecret = {SessionSecret}
|
||||
HashIDSalt = {HashIDSalt}
|
||||
`
|
||||
|
||||
// mapSection 将配置文件的 Section 映射到结构体上
|
||||
func mapSection(cfg *ini.File, section string, confStruct interface{}) error {
|
||||
err := cfg.Section(section).MapTo(confStruct)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 验证合法性
|
||||
validate := validator.New()
|
||||
err = validate.Struct(confStruct)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func getOverrideConfFromEnv(l logging.Logger) string {
|
||||
confMaps := make(map[string]map[string]string)
|
||||
for _, env := range os.Environ() {
|
||||
if !strings.HasPrefix(env, envConfOverrideKey) {
|
||||
continue
|
||||
}
|
||||
|
||||
// split by key=value and get key
|
||||
kv := strings.SplitN(env, "=", 2)
|
||||
if len(kv) < 2 {
|
||||
continue
|
||||
}
|
||||
configKey := strings.TrimPrefix(kv[0], envConfOverrideKey)
|
||||
configValue := kv[1]
|
||||
|
||||
// Prefer the native "." separator. Fall back to "__" so that TOML-based
|
||||
// deployment platforms (Fly.io, some Docker Compose variants) that
|
||||
// mangle dotted env var names can still express Section/Key overrides
|
||||
// as CR_CONF_Section__Key.
|
||||
sectionKey := strings.SplitN(configKey, ".", 2)
|
||||
if len(sectionKey) < 2 {
|
||||
sectionKey = strings.SplitN(configKey, "__", 2)
|
||||
}
|
||||
if len(sectionKey) < 2 || sectionKey[0] == "" || sectionKey[1] == "" {
|
||||
l.Warning("Skipping malformed %s override %q: expected %sSection.Key or %sSection__Key format",
|
||||
envConfOverrideKey, kv[0], envConfOverrideKey, envConfOverrideKey)
|
||||
continue
|
||||
}
|
||||
|
||||
if confMaps[sectionKey[0]] == nil {
|
||||
confMaps[sectionKey[0]] = make(map[string]string)
|
||||
}
|
||||
|
||||
confMaps[sectionKey[0]][sectionKey[1]] = configValue
|
||||
l.Info("Override config %q = %q", configKey, configValue)
|
||||
}
|
||||
|
||||
// generate ini content
|
||||
var sb strings.Builder
|
||||
for section, kvs := range confMaps {
|
||||
sb.WriteString(fmt.Sprintf("[%s]\n", section))
|
||||
for k, v := range kvs {
|
||||
sb.WriteString(fmt.Sprintf("%s = %s\n", k, v))
|
||||
}
|
||||
}
|
||||
|
||||
return sb.String()
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
package conf
|
||||
|
||||
import (
|
||||
"github.com/cloudreve/Cloudreve/v4/pkg/util"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// 测试Init日志路径错误
|
||||
func TestInitPanic(t *testing.T) {
|
||||
asserts := assert.New(t)
|
||||
|
||||
// 日志路径不存在时
|
||||
asserts.NotPanics(func() {
|
||||
Init("not/exist/path")
|
||||
})
|
||||
|
||||
asserts.True(util.Exists("conf.ini"))
|
||||
|
||||
}
|
||||
|
||||
// TestInitDelimiterNotFound 日志路径存在但 Key 格式错误时
|
||||
func TestInitDelimiterNotFound(t *testing.T) {
|
||||
asserts := assert.New(t)
|
||||
testCase := `[Database]
|
||||
Type = mysql
|
||||
User = root
|
||||
Password233root
|
||||
Host = 127.0.0.1:3306
|
||||
Name = v3
|
||||
TablePrefix = v3_`
|
||||
err := ioutil.WriteFile("testConf.ini", []byte(testCase), 0644)
|
||||
defer func() { err = os.Remove("testConf.ini") }()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
asserts.Panics(func() {
|
||||
Init("testConf.ini")
|
||||
})
|
||||
}
|
||||
|
||||
// TestInitNoPanic 日志路径存在且合法时
|
||||
func TestInitNoPanic(t *testing.T) {
|
||||
asserts := assert.New(t)
|
||||
testCase := `
|
||||
[System]
|
||||
Listen = 3000
|
||||
HashIDSalt = 1
|
||||
|
||||
[Database]
|
||||
Type = mysql
|
||||
User = root
|
||||
Password = root
|
||||
Host = 127.0.0.1:3306
|
||||
Name = v3
|
||||
TablePrefix = v3_`
|
||||
err := ioutil.WriteFile("testConf.ini", []byte(testCase), 0644)
|
||||
defer func() { err = os.Remove("testConf.ini") }()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
asserts.NotPanics(func() {
|
||||
Init("testConf.ini")
|
||||
})
|
||||
}
|
||||
|
||||
func TestMapSection(t *testing.T) {
|
||||
asserts := assert.New(t)
|
||||
|
||||
//正常情况
|
||||
testCase := `
|
||||
[System]
|
||||
Listen = 3000
|
||||
HashIDSalt = 1
|
||||
|
||||
[Database]
|
||||
Type = mysql
|
||||
User = root
|
||||
Password:root
|
||||
Host = 127.0.0.1:3306
|
||||
Name = v3
|
||||
TablePrefix = v3_`
|
||||
err := ioutil.WriteFile("testConf.ini", []byte(testCase), 0644)
|
||||
defer func() { err = os.Remove("testConf.ini") }()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
Init("testConf.ini")
|
||||
err = mapSection("Database", DatabaseConfig)
|
||||
asserts.NoError(err)
|
||||
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
package conf
|
||||
|
||||
import "github.com/cloudreve/Cloudreve/v4/pkg/util"
|
||||
|
||||
type DBType string
|
||||
|
||||
var (
|
||||
SQLiteDB DBType = "sqlite"
|
||||
SQLite3DB DBType = "sqlite3"
|
||||
MySqlDB DBType = "mysql"
|
||||
MsSqlDB DBType = "mssql"
|
||||
PostgresDB DBType = "postgres"
|
||||
MariaDB DBType = "mariadb"
|
||||
)
|
||||
|
||||
// Database 数据库
|
||||
type Database struct {
|
||||
Type DBType
|
||||
User string
|
||||
Password string
|
||||
Host string
|
||||
Name string
|
||||
TablePrefix string
|
||||
DBFile string
|
||||
Port int
|
||||
Charset string
|
||||
UnixSocket bool
|
||||
// 允许直接使用DATABASE_URL来配置数据库连接
|
||||
DatabaseURL string
|
||||
// SSLMode 允许使用SSL连接数据库, 用户可以在sslmode string中添加证书等配置
|
||||
SSLMode string
|
||||
}
|
||||
|
||||
type SysMode string
|
||||
|
||||
var (
|
||||
MasterMode SysMode = "master"
|
||||
SlaveMode SysMode = "slave"
|
||||
)
|
||||
|
||||
// System 系统通用配置
|
||||
type System struct {
|
||||
Mode SysMode `validate:"eq=master|eq=slave"`
|
||||
Listen string `validate:"required"`
|
||||
Debug bool
|
||||
SessionSecret string
|
||||
HashIDSalt string // deprecated
|
||||
GracePeriod int `validate:"gte=0"`
|
||||
ProxyHeader string
|
||||
LogLevel string `validate:"oneof=debug info warning error"`
|
||||
Pprof string // Address to listen for pprof, e.g. "localhost:6060". Empty to disable.
|
||||
}
|
||||
|
||||
type SSL struct {
|
||||
CertPath string `validate:"omitempty,required"`
|
||||
KeyPath string `validate:"omitempty,required"`
|
||||
Listen string `validate:"required"`
|
||||
}
|
||||
|
||||
type Unix struct {
|
||||
Listen string
|
||||
Perm uint32
|
||||
}
|
||||
|
||||
// Slave 作为slave存储端配置
|
||||
type Slave struct {
|
||||
Secret string `validate:"omitempty,gte=64"`
|
||||
CallbackTimeout int `validate:"omitempty,gte=1"`
|
||||
SignatureTTL int `validate:"omitempty,gte=1"`
|
||||
}
|
||||
|
||||
// Redis 配置
|
||||
type Redis struct {
|
||||
Network string
|
||||
Server string
|
||||
User string
|
||||
Password string
|
||||
DB string
|
||||
UseTLS bool
|
||||
TLSSkipVerify bool
|
||||
}
|
||||
|
||||
// 跨域配置
|
||||
type Cors struct {
|
||||
AllowOrigins []string
|
||||
AllowMethods []string
|
||||
AllowHeaders []string
|
||||
AllowCredentials bool
|
||||
ExposeHeaders []string
|
||||
SameSite string
|
||||
Secure bool
|
||||
}
|
||||
|
||||
// RedisConfig Redis服务器配置
|
||||
var RedisConfig = &Redis{
|
||||
Network: "tcp",
|
||||
Server: "",
|
||||
Password: "",
|
||||
DB: "0",
|
||||
UseTLS: false,
|
||||
TLSSkipVerify: true,
|
||||
}
|
||||
|
||||
// DatabaseConfig 数据库配置
|
||||
var DatabaseConfig = &Database{
|
||||
Charset: "utf8mb4",
|
||||
DBFile: util.DataPath("cloudreve.db"),
|
||||
Port: 3306,
|
||||
UnixSocket: false,
|
||||
DatabaseURL: "",
|
||||
}
|
||||
|
||||
// SystemConfig 系统公用配置
|
||||
var SystemConfig = &System{
|
||||
Debug: false,
|
||||
Mode: MasterMode,
|
||||
Listen: ":5212",
|
||||
ProxyHeader: "",
|
||||
LogLevel: "info",
|
||||
}
|
||||
|
||||
// CORSConfig 跨域配置
|
||||
var CORSConfig = &Cors{
|
||||
AllowOrigins: []string{"UNSET"},
|
||||
AllowMethods: []string{"PUT", "POST", "GET", "OPTIONS"},
|
||||
AllowHeaders: []string{"Cookie", "X-Cr-Policy", "Authorization", "Content-Length", "Content-Type", "X-Cr-Path", "X-Cr-FileName"},
|
||||
AllowCredentials: false,
|
||||
ExposeHeaders: nil,
|
||||
SameSite: "Default",
|
||||
Secure: false,
|
||||
}
|
||||
|
||||
// SlaveConfig 从机配置
|
||||
var SlaveConfig = &Slave{
|
||||
CallbackTimeout: 20,
|
||||
SignatureTTL: 600,
|
||||
}
|
||||
|
||||
var SSLConfig = &SSL{
|
||||
Listen: ":443",
|
||||
CertPath: "",
|
||||
KeyPath: "",
|
||||
}
|
||||
|
||||
var UnixConfig = &Unix{
|
||||
Listen: "",
|
||||
}
|
||||
|
||||
var OptionOverwrite = map[string]interface{}{}
|
||||
Reference in New Issue
Block a user