Init V4 community edition (#2265)
* Init V4 community edition * Init V4 community edition
This commit is contained in:
255
pkg/conf/conf.go
255
pkg/conf/conf.go
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
138
pkg/conf/types.go
Normal 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{}{}
|
||||
@@ -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"
|
||||
Reference in New Issue
Block a user