diff --git a/internal/admin/handler.go b/internal/admin/handler.go index 5e4fe96b4..9153c3c4a 100644 --- a/internal/admin/handler.go +++ b/internal/admin/handler.go @@ -473,7 +473,14 @@ func (h *Handler) ListRoles(c *gin.Context) { return } - success(c, roles, "") + if roles == nil { + roles = []map[string]interface{}{} + } + + success(c, gin.H{ + "roles": roles, + "total": len(roles), + }, "") } // CreateRoleHTTPRequest create role request diff --git a/internal/server/config.go b/internal/server/config.go index 98bc8bb75..1bc2c9847 100644 --- a/internal/server/config.go +++ b/internal/server/config.go @@ -18,6 +18,7 @@ package server import ( "fmt" + "net/mail" "net/url" "os" "strconv" @@ -33,15 +34,18 @@ const DefaultConnectTimeout = 5 * time.Second // Config application configuration type Config struct { - Server ServerConfig `mapstructure:"server"` - Database DatabaseConfig `mapstructure:"database"` - Redis RedisConfig `mapstructure:"redis"` - Log LogConfig `mapstructure:"log"` - DocEngine DocEngineConfig `mapstructure:"doc_engine"` - RegisterEnabled int `mapstructure:"register_enabled"` - OAuth map[string]OAuthConfig `mapstructure:"oauth"` - Admin AdminConfig `mapstructure:"admin"` - UserDefaultLLM UserDefaultLLMConfig `mapstructure:"user_default_llm"` + Server ServerConfig `mapstructure:"server"` + Database DatabaseConfig `mapstructure:"database"` + Redis RedisConfig `mapstructure:"redis"` + Log LogConfig `mapstructure:"log"` + DocEngine DocEngineConfig `mapstructure:"doc_engine"` + StorageEngine StorageConfig `mapstructure:"storage_engine"` + RegisterEnabled int `mapstructure:"register_enabled"` + OAuth map[string]OAuthConfig `mapstructure:"oauth"` + Admin AdminConfig `mapstructure:"admin"` + UserDefaultLLM UserDefaultLLMConfig `mapstructure:"user_default_llm"` + DefaultSuperUser DefaultSuperUser `mapstructure:"default_super_user"` + Language string `mapstructure:"language"` } // AdminConfig admin server configuration @@ -50,6 +54,12 @@ type AdminConfig struct { Port int `mapstructure:"http_port"` } +type DefaultSuperUser struct { + Email string `mapstructure:"email"` + Password string `mapstructure:"password"` + Nickname string `mapstructure:"nickname"` +} + // UserDefaultLLMConfig user default LLM configuration type UserDefaultLLMConfig struct { DefaultModels DefaultModelsConfig `mapstructure:"default_models"` @@ -130,6 +140,59 @@ type InfinityConfig struct { DBName string `mapstructure:"db_name"` } +type StorageType string + +// StorageConfig holds all storage-related configurations +type StorageConfig struct { + Type StorageType `mapstructure:"type"` + Minio *MinioConfig `mapstructure:"minio"` + S3 *S3Config `mapstructure:"s3"` + OSS *OSSConfig `mapstructure:"oss"` +} + +const ( + StorageOSS StorageType = "oss" + StorageS3 StorageType = "s3" + StorageMinio StorageType = "minio" +) + +// OSSConfig holds Aliyun OSS storage configuration +// OSS is compatible with S3 API +type OSSConfig struct { + AccessKeyID string `mapstructure:"access_key"` // OSS Access Key ID + SecretAccessKey string `mapstructure:"secret_key"` // OSS Secret Access Key + EndpointURL string `mapstructure:"endpoint_url"` // OSS Endpoint (e.g., "https://oss-cn-hangzhou.aliyuncs.com") + Region string `mapstructure:"region"` // Region (e.g., "cn-hangzhou") + Bucket string `mapstructure:"bucket"` // Default bucket (optional) + PrefixPath string `mapstructure:"prefix_path"` // Path prefix (optional) + SignatureVersion string `mapstructure:"signature_version"` // Signature version + AddressingStyle string `mapstructure:"addressing_style"` // Addressing style +} + +// MinioConfig holds MinIO storage configuration +type MinioConfig struct { + Host string `mapstructure:"host"` // MinIO server host (e.g., "localhost:9000") + User string `mapstructure:"user"` // Access key + Password string `mapstructure:"password"` // Secret key + Secure bool `mapstructure:"secure"` // Use HTTPS + Verify bool `mapstructure:"verify"` // Verify SSL certificates + Bucket string `mapstructure:"bucket"` // Default bucket (optional) + PrefixPath string `mapstructure:"prefix_path"` // Path prefix (optional) +} + +// S3Config holds AWS S3 storage configuration +type S3Config struct { + AccessKeyID string `mapstructure:"access_key"` // AWS Access Key ID + SecretAccessKey string `mapstructure:"secret_key"` // AWS Secret Access Key + SessionToken string `mapstructure:"session_token"` // AWS Session Token (optional) + Region string `mapstructure:"region_name"` // AWS Region + EndpointURL string `mapstructure:"endpoint_url"` // Custom endpoint (optional) + SignatureVersion string `mapstructure:"signature_version"` // Signature version + AddressingStyle string `mapstructure:"addressing_style"` // Addressing style + Bucket string `mapstructure:"bucket"` // Default bucket (optional) + PrefixPath string `mapstructure:"prefix_path"` // Path prefix (optional) +} + // RedisConfig Redis configuration type RedisConfig struct { Host string `mapstructure:"host"` @@ -147,42 +210,17 @@ var ( // Init initialize configuration func Init(configPath string) error { - v := viper.New() - // Set configuration file path - if configPath != "" { - v.SetConfigFile(configPath) - } else { - // Try to load service_conf.yaml from conf directory first - v.SetConfigName("service_conf") - v.SetConfigType("yaml") - v.AddConfigPath("./conf") - v.AddConfigPath(".") - v.AddConfigPath("./config") - v.AddConfigPath("./internal/config") - v.AddConfigPath("/etc/ragflow/") + err := FromConfigFile("") + if err != nil { + return err } - // Read environment variables - v.SetEnvPrefix("RAGFLOW") - v.SetEnvKeyReplacer(strings.NewReplacer(".", "_")) - v.AutomaticEnv() - - // Read configuration file - if err := v.ReadInConfig(); err != nil { - if _, ok := err.(viper.ConfigFileNotFoundError); !ok { - return fmt.Errorf("read config file error: %w", err) - } - zapLogger.Info("Config file not found, using environment variables only") + err = FromEnvironments() + if err != nil { + return err } - // Save viper instance - globalViper = v - - docEngine := os.Getenv("DOC_ENGINE") - if docEngine == "" { - docEngine = "elasticsearch" - } id := 0 for k, v := range globalViper.AllSettings() { configDict, ok := v.(map[string]interface{}) @@ -200,7 +238,7 @@ func Init(configPath string) error { delete(configDict, "http_port") case "es": // Skip if retrieval_type doesn't match doc_engine - if docEngine != "elasticsearch" { + if globalConfig.DocEngine.Type != "elasticsearch" { continue } hosts := getString(configDict, "hosts") @@ -222,7 +260,7 @@ func Init(configPath string) error { delete(configDict, "password") case "infinity": // Skip if retrieval_type doesn't match doc_engine - if docEngine != "infinity" { + if globalConfig.DocEngine.Type != "infinity" { continue } uri := getString(configDict, "uri") @@ -326,6 +364,120 @@ func Init(configPath string) error { id++ } + return nil +} + +func FromEnvironments() error { + // Doc engine + docEngine := strings.ToLower(os.Getenv("DOC_ENGINE")) + switch docEngine { + case "infinity": + globalConfig.DocEngine.Type = EngineInfinity + case "": + // Default + if globalConfig.DocEngine.Type == "" { + globalConfig.DocEngine.Type = EngineElasticsearch + } + case "elasticsearch": + globalConfig.DocEngine.Type = EngineElasticsearch + case "opensearch": + case "oceanbase": + return fmt.Errorf("not implemented: %s", docEngine) + default: + return fmt.Errorf("invalid doc engine: %s", docEngine) + } + + // Default super user email + globalConfig.DefaultSuperUser.Email = "admin@ragflow.io" + superUserEmail := os.Getenv("DEFAULT_SUPERUSER_EMAIL") + if superUserEmail != "" { + _, err := mail.ParseAddress(superUserEmail) + if err != nil { + return fmt.Errorf("invalid super user email: %s", superUserEmail) + } + globalConfig.DefaultSuperUser.Email = superUserEmail + } + + globalConfig.DefaultSuperUser.Password = "admin" + superUserPassword := os.Getenv("DEFAULT_SUPERUSER_PASSWORD") + if superUserPassword != "" { + globalConfig.DefaultSuperUser.Password = superUserPassword + } + + globalConfig.DefaultSuperUser.Nickname = "admin" + superUserNickname := os.Getenv("DEFAULT_SUPERUSER_NICKNAME") + if superUserNickname != "" { + globalConfig.DefaultSuperUser.Nickname = superUserNickname + } + + // Meta database + databaseType := strings.ToLower(os.Getenv("DB_TYPE")) + switch databaseType { + case "mysql": + globalConfig.Database.Driver = "mysql" + case "": + // Default + if globalConfig.Database.Driver == "" { + globalConfig.Database.Driver = "mysql" + } + default: + return fmt.Errorf("invalid database type: %s", databaseType) + } + + // Storage + storageType := strings.ToLower(os.Getenv("STORAGE_IMPL")) + switch storageType { + case "minio": + globalConfig.StorageEngine.Type = StorageMinio + case "s3": + globalConfig.StorageEngine.Type = StorageS3 + case "oss": + globalConfig.StorageEngine.Type = StorageOSS + default: + return fmt.Errorf("invalid storage type: %s", storageType) + } + + // Language + if globalConfig.Language == "" { + globalConfig.Language = GetLanguage() + } + + return nil +} + +func FromConfigFile(configPath string) error { + v := viper.New() + + // Set configuration file path + if configPath != "" { + v.SetConfigFile(configPath) + } else { + // Try to load service_conf.yaml from conf directory first + v.SetConfigName("service_conf") + v.SetConfigType("yaml") + v.AddConfigPath("./conf") + v.AddConfigPath(".") + v.AddConfigPath("./config") + v.AddConfigPath("./internal/config") + v.AddConfigPath("/etc/ragflow/") + } + + // Read environment variables + v.SetEnvPrefix("RAGFLOW") + v.SetEnvKeyReplacer(strings.NewReplacer(".", "_")) + v.AutomaticEnv() + + // Read configuration file + if err := v.ReadInConfig(); err != nil { + if _, ok := err.(viper.ConfigFileNotFoundError); !ok { + return fmt.Errorf("read config file error: %w", err) + } + zapLogger.Info("Config file not found, using environment variables only") + } + + // Save viper instance + globalViper = v + // Unmarshal configuration to globalConfig // Note: This will only unmarshal fields that match the Config struct if err := v.Unmarshal(&globalConfig); err != nil { @@ -415,9 +567,9 @@ func Init(configPath string) error { // Map doc_engine section to DocEngineConfig if globalConfig != nil && globalConfig.DocEngine.Type == "" { // Use DOC_ENGINE env var if set - if docEngine != "" { - globalConfig.DocEngine.Type = EngineType(docEngine) - } + //if docEngine != "" { + // globalConfig.DocEngine.Type = EngineType(docEngine) + //} // Try to map from doc_engine section (overrides env var if present) if v.IsSet("doc_engine") { docEngineConfig := v.Sub("doc_engine") @@ -583,3 +735,20 @@ func getInt(m map[string]interface{}, key string) int { } return 0 } + +func GetLanguage() string { + lang := os.Getenv("LANG") + if lang == "" { + lang = os.Getenv("LANGUAGE") + } + + lang = strings.ToLower(lang) + + if strings.Contains(lang, "zh_") || + strings.Contains(lang, "zh-") || + strings.HasPrefix(lang, "zh") { + return "Chinese" + } + + return "English" +} diff --git a/internal/storage/minio.go b/internal/storage/minio.go index 280694efc..7c0c02746 100644 --- a/internal/storage/minio.go +++ b/internal/storage/minio.go @@ -29,17 +29,6 @@ import ( "go.uber.org/zap" ) -// MinioConfig holds MinIO storage configuration -type MinioConfig struct { - Host string `mapstructure:"host"` // MinIO server host (e.g., "localhost:9000") - User string `mapstructure:"user"` // Access key - Password string `mapstructure:"password"` // Secret key - Secure bool `mapstructure:"secure"` // Use HTTPS - Verify bool `mapstructure:"verify"` // Verify SSL certificates - Bucket string `mapstructure:"bucket"` // Default bucket (optional) - PrefixPath string `mapstructure:"prefix_path"` // Path prefix (optional) -} - // MinioStorage implements Storage interface for MinIO type MinioStorage struct { client *minio.Client diff --git a/internal/storage/oss.go b/internal/storage/oss.go index d74aa9da7..a61baa6cb 100644 --- a/internal/storage/oss.go +++ b/internal/storage/oss.go @@ -31,19 +31,6 @@ import ( "go.uber.org/zap" ) -// OSSConfig holds Aliyun OSS storage configuration -// OSS is compatible with S3 API -type OSSConfig struct { - AccessKeyID string `mapstructure:"access_key"` // OSS Access Key ID - SecretAccessKey string `mapstructure:"secret_key"` // OSS Secret Access Key - EndpointURL string `mapstructure:"endpoint_url"` // OSS Endpoint (e.g., "https://oss-cn-hangzhou.aliyuncs.com") - Region string `mapstructure:"region"` // Region (e.g., "cn-hangzhou") - Bucket string `mapstructure:"bucket"` // Default bucket (optional) - PrefixPath string `mapstructure:"prefix_path"` // Path prefix (optional) - SignatureVersion string `mapstructure:"signature_version"` // Signature version - AddressingStyle string `mapstructure:"addressing_style"` // Addressing style -} - // OSSStorage implements Storage interface for Aliyun OSS // OSS uses S3-compatible API type OSSStorage struct { diff --git a/internal/storage/s3.go b/internal/storage/s3.go index af49fbd46..6533e9a4c 100644 --- a/internal/storage/s3.go +++ b/internal/storage/s3.go @@ -31,19 +31,6 @@ import ( "go.uber.org/zap" ) -// S3Config holds AWS S3 storage configuration -type S3Config struct { - AccessKeyID string `mapstructure:"access_key"` // AWS Access Key ID - SecretAccessKey string `mapstructure:"secret_key"` // AWS Secret Access Key - SessionToken string `mapstructure:"session_token"` // AWS Session Token (optional) - Region string `mapstructure:"region_name"` // AWS Region - EndpointURL string `mapstructure:"endpoint_url"` // Custom endpoint (optional) - SignatureVersion string `mapstructure:"signature_version"` // Signature version - AddressingStyle string `mapstructure:"addressing_style"` // Addressing style - Bucket string `mapstructure:"bucket"` // Default bucket (optional) - PrefixPath string `mapstructure:"prefix_path"` // Path prefix (optional) -} - // S3Storage implements Storage interface for AWS S3 type S3Storage struct { client *s3.Client diff --git a/internal/storage/storage_factory.go b/internal/storage/storage_factory.go index e7f17d058..d63ef5017 100644 --- a/internal/storage/storage_factory.go +++ b/internal/storage/storage_factory.go @@ -19,45 +19,26 @@ package storage import ( "fmt" "os" + "ragflow/internal/server" "sync" "github.com/spf13/viper" "go.uber.org/zap" ) -// StorageFactory creates storage instances based on configuration -type StorageFactory struct { - storageType StorageType - storage Storage - config *StorageConfig - mu sync.RWMutex -} - -// StorageConfig holds all storage-related configurations -type StorageConfig struct { - StorageType string `mapstructure:"storage_type"` - Minio *MinioConfig `mapstructure:"minio"` - S3 *S3Config `mapstructure:"s3"` - OSS *OSSConfig `mapstructure:"oss"` -} - -// AzureConfig holds Azure-specific configurations -type AzureConfig struct { - ContainerURL string `mapstructure:"container_url"` - SASToken string `mapstructure:"sas_token"` - AccountURL string `mapstructure:"account_url"` - ClientID string `mapstructure:"client_id"` - Secret string `mapstructure:"secret"` - TenantID string `mapstructure:"tenant_id"` - ContainerName string `mapstructure:"container_name"` - AuthorityHost string `mapstructure:"authority_host"` -} - var ( globalFactory *StorageFactory once sync.Once ) +// StorageFactory creates storage instances based on configuration +type StorageFactory struct { + storageType StorageType + storage Storage + config *server.StorageConfig + mu sync.RWMutex +} + // GetStorageFactory returns the singleton storage factory instance func GetStorageFactory() *StorageFactory { once.Do(func() { @@ -79,7 +60,7 @@ func InitStorageFactory(v *viper.Viper) error { storageType = "MINIO" // Default storage type } - storageConfig := &StorageConfig{} + storageConfig := &server.StorageConfig{} if err := v.UnmarshalKey("storage", storageConfig); err != nil { return fmt.Errorf("failed to unmarshal storage config: %w", err) } @@ -114,7 +95,7 @@ func (f *StorageFactory) initStorage(storageType string, v *viper.Viper) error { } func (f *StorageFactory) initMinio(v *viper.Viper) error { - config := &MinioConfig{} + config := &server.MinioConfig{} // Try to load from minio section first if v.IsSet("minio") { @@ -156,7 +137,7 @@ func (f *StorageFactory) initMinio(v *viper.Viper) error { } func (f *StorageFactory) initS3(v *viper.Viper) error { - config := &S3Config{} + config := &server.S3Config{} if v.IsSet("s3") { s3Config := v.Sub("s3") @@ -188,7 +169,7 @@ func (f *StorageFactory) initS3(v *viper.Viper) error { } func (f *StorageFactory) initOSS(v *viper.Viper) error { - config := &OSSConfig{} + config := &server.OSSConfig{} if v.IsSet("oss") { ossConfig := v.Sub("oss") @@ -276,20 +257,20 @@ func (f *StorageFactory) SetStorage(storage Storage) { } // StorageTypeMapping returns the storage type mapping (equivalent to Python's storage_mapping) -var StorageTypeMapping = map[StorageType]func(*StorageConfig) (Storage, error){ - StorageMinio: func(config *StorageConfig) (Storage, error) { +var StorageTypeMapping = map[StorageType]func(*server.StorageConfig) (Storage, error){ + StorageMinio: func(config *server.StorageConfig) (Storage, error) { if config.Minio == nil { return nil, fmt.Errorf("MinIO config not available") } return NewMinioStorage(config.Minio) }, - StorageAWSS3: func(config *StorageConfig) (Storage, error) { + StorageAWSS3: func(config *server.StorageConfig) (Storage, error) { if config.S3 == nil { return nil, fmt.Errorf("S3 config not available") } return NewS3Storage(config.S3) }, - StorageOSS: func(config *StorageConfig) (Storage, error) { + StorageOSS: func(config *server.StorageConfig) (Storage, error) { if config.OSS == nil { return nil, fmt.Errorf("OSS config not available") }