Init V4 community edition (#2265)

* Init V4 community edition

* Init V4 community edition
This commit is contained in:
AaronLiu
2025-04-20 17:31:25 +08:00
committed by GitHub
parent da4e44b77a
commit 21d158db07
597 changed files with 119415 additions and 41692 deletions

View File

@@ -1,75 +1,135 @@
package conf
import (
"github.com/cloudreve/Cloudreve/v3/pkg/util"
"fmt"
"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"
"os"
"strings"
)
// database 数据库
type database struct {
Type string
User string
Password string
Host string
Name string
TablePrefix string
DBFile string
Port int
Charset string
UnixSocket bool
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
}
// system 系统通用配置
type system struct {
Mode string `validate:"eq=master|eq=slave"`
Listen string `validate:"required"`
Debug bool
SessionSecret string
HashIDSalt string
GracePeriod int `validate:"gte=0"`
ProxyHeader string `validate:"required_with=Listen"`
// 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.RandStringRunes(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 ssl struct {
CertPath string `validate:"omitempty,required"`
KeyPath string `validate:"omitempty,required"`
Listen string `validate:"required"`
type iniConfigProvider struct {
database Database
system System
ssl SSL
unix Unix
slave Slave
redis Redis
cors Cors
optionOverwrite map[string]any
}
type unix struct {
Listen string
Perm uint32
func (i *iniConfigProvider) Database() *Database {
return &i.database
}
// slave 作为slave存储端配置
type slave struct {
Secret string `validate:"omitempty,gte=64"`
CallbackTimeout int `validate:"omitempty,gte=1"`
SignatureTTL int `validate:"omitempty,gte=1"`
func (i *iniConfigProvider) System() *System {
return &i.system
}
// redis 配置
type redis struct {
Network string
Server string
User string
Password string
DB string
func (i *iniConfigProvider) SSL() *SSL {
return &i.ssl
}
// 跨域配置
type cors struct {
AllowOrigins []string
AllowMethods []string
AllowHeaders []string
AllowCredentials bool
ExposeHeaders []string
SameSite string
Secure bool
func (i *iniConfigProvider) Unix() *Unix {
return &i.unix
}
var cfg *ini.File
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
@@ -79,67 +139,8 @@ SessionSecret = {SessionSecret}
HashIDSalt = {HashIDSalt}
`
// Init 初始化配置文件
func Init(path string) {
var err error
if path == "" || !util.Exists(path) {
// 创建初始配置文件
confContent := util.Replace(map[string]string{
"{SessionSecret}": util.RandStringRunes(64),
"{HashIDSalt}": util.RandStringRunes(64),
}, defaultConf)
f, err := util.CreatNestedFile(path)
if err != nil {
util.Log().Panic("Failed to create config file: %s", err)
}
// 写入配置文件
_, err = f.WriteString(confContent)
if err != nil {
util.Log().Panic("Failed to write config file: %s", err)
}
f.Close()
}
cfg, err = ini.Load(path)
if err != nil {
util.Log().Panic("Failed to parse config file %q: %s", path, err)
}
sections := map[string]interface{}{
"Database": DatabaseConfig,
"System": SystemConfig,
"SSL": SSLConfig,
"UnixSocket": UnixConfig,
"Redis": RedisConfig,
"CORS": CORSConfig,
"Slave": SlaveConfig,
}
for sectionName, sectionStruct := range sections {
err = mapSection(sectionName, sectionStruct)
if err != nil {
util.Log().Panic("Failed to parse config section %q: %s", sectionName, err)
}
}
// 映射数据库配置覆盖
for _, key := range cfg.Section("OptionOverwrite").Keys() {
OptionOverwrite[key.Name()] = key.Value()
}
// 重设log等级
if !SystemConfig.Debug {
util.Level = util.LevelInformational
util.GloablLogger = nil
util.Log()
}
}
// mapSection 将配置文件的 Section 映射到结构体上
func mapSection(section string, confStruct interface{}) error {
func mapSection(cfg *ini.File, section string, confStruct interface{}) error {
err := cfg.Section(section).MapTo(confStruct)
if err != nil {
return err
@@ -154,3 +155,35 @@ func mapSection(section string, confStruct interface{}) error {
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)
configKey := strings.TrimPrefix(kv[0], envConfOverrideKey)
configValue := kv[1]
sectionKey := strings.SplitN(configKey, ".", 2)
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()
}

View File

@@ -1,12 +1,11 @@
package conf
import (
"github.com/cloudreve/Cloudreve/v4/pkg/util"
"github.com/stretchr/testify/assert"
"io/ioutil"
"os"
"testing"
"github.com/cloudreve/Cloudreve/v3/pkg/util"
"github.com/stretchr/testify/assert"
)
// 测试Init日志路径错误
@@ -15,10 +14,10 @@ func TestInitPanic(t *testing.T) {
// 日志路径不存在时
asserts.NotPanics(func() {
Init("not/exist/path/conf.ini")
Init("not/exist/path")
})
asserts.True(util.Exists("not/exist/path/conf.ini"))
asserts.True(util.Exists("conf.ini"))
}
@@ -56,11 +55,7 @@ User = root
Password = root
Host = 127.0.0.1:3306
Name = v3
TablePrefix = v3_
[OptionOverwrite]
key=value
`
TablePrefix = v3_`
err := ioutil.WriteFile("testConf.ini", []byte(testCase), 0644)
defer func() { err = os.Remove("testConf.ini") }()
if err != nil {
@@ -69,7 +64,6 @@ key=value
asserts.NotPanics(func() {
Init("testConf.ini")
})
asserts.Equal(OptionOverwrite["key"], "value")
}
func TestMapSection(t *testing.T) {

View File

@@ -1,55 +0,0 @@
package conf
// RedisConfig Redis服务器配置
var RedisConfig = &redis{
Network: "tcp",
Server: "",
Password: "",
DB: "0",
}
// DatabaseConfig 数据库配置
var DatabaseConfig = &database{
Type: "UNSET",
Charset: "utf8",
DBFile: "cloudreve.db",
Port: 3306,
UnixSocket: false,
}
// SystemConfig 系统公用配置
var SystemConfig = &system{
Debug: false,
Mode: "master",
Listen: ":5212",
ProxyHeader: "X-Forwarded-For",
}
// 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: 60,
}
var SSLConfig = &ssl{
Listen: ":443",
CertPath: "",
KeyPath: "",
}
var UnixConfig = &unix{
Listen: "",
}
var OptionOverwrite = map[string]interface{}{}

138
pkg/conf/types.go Normal file
View File

@@ -0,0 +1,138 @@
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"
)
// Database 数据库
type Database struct {
Type DBType
User string
Password string
Host string
Name string
TablePrefix string
DBFile string
Port int
Charset string
UnixSocket bool
}
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 `validate:"required_with=Listen"`
LogLevel string `validate:"oneof=debug info warning error"`
}
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
}
// 跨域配置
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",
}
// DatabaseConfig 数据库配置
var DatabaseConfig = &Database{
Charset: "utf8mb4",
DBFile: util.DataPath("cloudreve.db"),
Port: 3306,
UnixSocket: false,
}
// SystemConfig 系统公用配置
var SystemConfig = &System{
Debug: false,
Mode: MasterMode,
Listen: ":5212",
ProxyHeader: "X-Forwarded-For",
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{}{}

View File

@@ -1,16 +0,0 @@
package conf
// BackendVersion 当前后端版本号
var BackendVersion = "3.8.3"
// RequiredDBVersion 与当前版本匹配的数据库版本
var RequiredDBVersion = "3.8.1"
// RequiredStaticVersion 与当前版本匹配的静态资源版本
var RequiredStaticVersion = "3.8.3"
// IsPro 是否为Pro版本
var IsPro = "false"
// LastCommit 最后commit id
var LastCommit = "a11f819"