Files
ragflow/internal/server/config.go
Jin Hai 74866371ef Fix compatiblity issue (#13667)
### What problem does this PR solve?

1. Change go admin server port from 9385 to 9383 to avoid conflicts
2. Start go server after python servers are started completely, in
entrypoint.sh
3. Fix some database migration issue
4. Add more API routes in web to compliant with EE.

### Type of change

- [x] Bug Fix (non-breaking change which fixes an issue)

Signed-off-by: Jin Hai <haijin.chn@gmail.com>
2026-03-18 11:51:03 +08:00

586 lines
17 KiB
Go

//
// Copyright 2026 The InfiniFlow Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
package server
import (
"fmt"
"net/url"
"os"
"strconv"
"strings"
"time"
"github.com/spf13/viper"
"go.uber.org/zap"
)
// DefaultConnectTimeout default connection timeout for external services
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"`
}
// AdminConfig admin server configuration
type AdminConfig struct {
Host string `mapstructure:"host"`
Port int `mapstructure:"http_port"`
}
// UserDefaultLLMConfig user default LLM configuration
type UserDefaultLLMConfig struct {
DefaultModels DefaultModelsConfig `mapstructure:"default_models"`
}
// DefaultModelsConfig default models configuration
type DefaultModelsConfig struct {
ChatModel ModelConfig `mapstructure:"chat_model"`
EmbeddingModel ModelConfig `mapstructure:"embedding_model"`
RerankModel ModelConfig `mapstructure:"rerank_model"`
ASRModel ModelConfig `mapstructure:"asr_model"`
Image2TextModel ModelConfig `mapstructure:"image2text_model"`
}
// ModelConfig model configuration
type ModelConfig struct {
Name string `mapstructure:"name"`
APIKey string `mapstructure:"api_key"`
BaseURL string `mapstructure:"base_url"`
Factory string `mapstructure:"factory"`
}
// OAuthConfig OAuth configuration for a channel
type OAuthConfig struct {
DisplayName string `mapstructure:"display_name"`
Icon string `mapstructure:"icon"`
}
// ServerConfig server configuration
type ServerConfig struct {
Mode string `mapstructure:"mode"` // debug, release
Port int `mapstructure:"port"`
}
// DatabaseConfig database configuration
type DatabaseConfig struct {
Driver string `mapstructure:"driver"` // mysql
Host string `mapstructure:"host"`
Port int `mapstructure:"port"`
Database string `mapstructure:"database"`
Username string `mapstructure:"username"`
Password string `mapstructure:"password"`
Charset string `mapstructure:"charset"`
}
// LogConfig logging configuration
type LogConfig struct {
Level string `mapstructure:"level"` // debug, info, warn, error
Format string `mapstructure:"format"` // json, text
}
// DocEngineConfig document engine configuration
type DocEngineConfig struct {
Type EngineType `mapstructure:"type"`
ES *ElasticsearchConfig `mapstructure:"es"`
Infinity *InfinityConfig `mapstructure:"infinity"`
}
// EngineType document engine type
type EngineType string
const (
EngineElasticsearch EngineType = "elasticsearch"
EngineInfinity EngineType = "infinity"
)
// ElasticsearchConfig Elasticsearch configuration
type ElasticsearchConfig struct {
Hosts string `mapstructure:"hosts"`
Username string `mapstructure:"username"`
Password string `mapstructure:"password"`
}
// InfinityConfig Infinity configuration
type InfinityConfig struct {
URI string `mapstructure:"uri"`
PostgresPort int `mapstructure:"postgres_port"`
DBName string `mapstructure:"db_name"`
}
// RedisConfig Redis configuration
type RedisConfig struct {
Host string `mapstructure:"host"`
Port int `mapstructure:"port"`
Password string `mapstructure:"password"`
DB int `mapstructure:"db"`
}
var (
globalConfig *Config
globalViper *viper.Viper
zapLogger *zap.Logger
allConfigs []map[string]interface{}
)
// 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/")
}
// 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
docEngine := os.Getenv("DOC_ENGINE")
if docEngine == "" {
docEngine = "elasticsearch"
}
id := 0
for k, v := range globalViper.AllSettings() {
configDict, ok := v.(map[string]interface{})
if !ok {
continue
}
switch k {
case "ragflow":
configDict["id"] = id
configDict["name"] = fmt.Sprintf("ragflow_%d", id)
configDict["service_type"] = "ragflow_server"
configDict["extra"] = map[string]interface{}{}
configDict["port"] = configDict["http_port"]
delete(configDict, "http_port")
case "es":
// Skip if retrieval_type doesn't match doc_engine
if docEngine != "elasticsearch" {
continue
}
hosts := getString(configDict, "hosts")
host, port := parseHostPort(hosts)
username := getString(configDict, "username")
password := getString(configDict, "password")
configDict["id"] = id
configDict["name"] = "elasticsearch"
configDict["host"] = host
configDict["port"] = port
configDict["service_type"] = "retrieval"
configDict["extra"] = map[string]interface{}{
"retrieval_type": "elasticsearch",
"username": username,
"password": password,
}
delete(configDict, "hosts")
delete(configDict, "username")
delete(configDict, "password")
case "infinity":
// Skip if retrieval_type doesn't match doc_engine
if docEngine != "infinity" {
continue
}
uri := getString(configDict, "uri")
host, port := parseHostPort(uri)
dbName := getString(configDict, "db_name")
if dbName == "" {
dbName = "default_db"
}
configDict["id"] = id
configDict["name"] = "infinity"
configDict["host"] = host
configDict["port"] = port
configDict["service_type"] = "retrieval"
configDict["extra"] = map[string]interface{}{
"retrieval_type": "infinity",
"db_name": dbName,
}
case "minio":
hostPort := getString(configDict, "host")
host, port := parseHostPort(hostPort)
user := getString(configDict, "user")
password := getString(configDict, "password")
configDict["id"] = id
configDict["name"] = "minio"
configDict["host"] = host
configDict["port"] = port
configDict["service_type"] = "file_store"
configDict["extra"] = map[string]interface{}{
"store_type": "minio",
"user": user,
"password": password,
}
delete(configDict, "bucket")
delete(configDict, "user")
delete(configDict, "password")
case "redis":
hostPort := getString(configDict, "host")
host, port := parseHostPort(hostPort)
password := getString(configDict, "password")
db := getInt(configDict, "db")
configDict["id"] = id
configDict["name"] = "redis"
configDict["host"] = host
configDict["port"] = port
configDict["service_type"] = "message_queue"
configDict["extra"] = map[string]interface{}{
"mq_type": "redis",
"database": db,
"password": password,
}
delete(configDict, "password")
delete(configDict, "db")
case "mysql":
host := getString(configDict, "host")
port := getInt(configDict, "port")
user := getString(configDict, "user")
password := getString(configDict, "password")
configDict["id"] = id
configDict["name"] = "mysql"
configDict["host"] = host
configDict["port"] = port
configDict["service_type"] = "meta_data"
configDict["extra"] = map[string]interface{}{
"meta_type": "mysql",
"username": user,
"password": password,
}
delete(configDict, "stale_timeout")
delete(configDict, "max_connections")
delete(configDict, "max_allowed_packet")
delete(configDict, "user")
delete(configDict, "password")
case "task_executor":
mqType := getString(configDict, "message_queue_type")
configDict["id"] = id
configDict["name"] = "task_executor"
configDict["service_type"] = "task_executor"
configDict["extra"] = map[string]interface{}{
"message_queue_type": mqType,
}
delete(configDict, "message_queue_type")
case "admin":
// Skip admin section
continue
default:
// Skip unknown sections
continue
}
// Set default values for empty host/port
if configDict["host"] == "" {
configDict["host"] = "-"
}
if configDict["port"] == 0 {
configDict["port"] = "-"
}
delete(configDict, "prefix_path")
delete(configDict, "username")
allConfigs = append(allConfigs, configDict)
id++
}
// Unmarshal configuration to globalConfig
// Note: This will only unmarshal fields that match the Config struct
if err := v.Unmarshal(&globalConfig); err != nil {
return fmt.Errorf("unmarshal config error: %w", err)
}
// Set default values for admin configuration if not configured
if globalConfig.Admin.Host == "" {
globalConfig.Admin.Host = "127.0.0.1"
}
if globalConfig.Admin.Port == 0 {
globalConfig.Admin.Port = 9383
} else {
globalConfig.Admin.Port += 2
}
// Load REGISTER_ENABLED from environment variable (default: 1)
registerEnabled := 1
if envVal := os.Getenv("REGISTER_ENABLED"); envVal != "" {
if parsed, err := strconv.Atoi(envVal); err == nil {
registerEnabled = parsed
}
}
globalConfig.RegisterEnabled = registerEnabled
// If we loaded service_conf.yaml, map mysql fields to DatabaseConfig
if globalConfig != nil && globalConfig.Database.Host == "" {
// Try to map from mysql section
if v.IsSet("mysql") {
mysqlConfig := v.Sub("mysql")
if mysqlConfig != nil {
globalConfig.Database.Driver = "mysql"
globalConfig.Database.Host = mysqlConfig.GetString("host")
globalConfig.Database.Port = mysqlConfig.GetInt("port")
globalConfig.Database.Database = mysqlConfig.GetString("name")
globalConfig.Database.Username = mysqlConfig.GetString("user")
globalConfig.Database.Password = mysqlConfig.GetString("password")
globalConfig.Database.Charset = "utf8mb4"
}
}
}
// Map ragflow section to ServerConfig
if globalConfig != nil && globalConfig.Server.Port == 0 {
// Try to map from ragflow section
if v.IsSet("ragflow") {
ragflowConfig := v.Sub("ragflow")
if ragflowConfig != nil {
globalConfig.Server.Port = ragflowConfig.GetInt("http_port") + 4 // 9384, by default
//globalConfig.Server.Port = ragflowConfig.GetInt("http_port") // Correct
// If mode is not set, default to debug
if globalConfig.Server.Mode == "" {
globalConfig.Server.Mode = "release"
}
}
}
}
// Map redis section to RedisConfig
if globalConfig != nil && globalConfig.Redis.Host != "" {
if v.IsSet("redis") {
redisConfig := v.Sub("redis")
if redisConfig != nil {
hostStr := redisConfig.GetString("host")
// Handle host:port format (e.g., "localhost:6379")
if hostStr == "" {
return fmt.Errorf("Empty host of redis configuration")
}
if idx := strings.LastIndex(hostStr, ":"); idx != -1 {
globalConfig.Redis.Host = hostStr[:idx]
if portStr := hostStr[idx+1:]; portStr != "" {
if port, err := strconv.Atoi(portStr); err == nil {
globalConfig.Redis.Port = port
}
}
} else {
return fmt.Errorf("Error address format of redis: %s", hostStr)
}
globalConfig.Redis.Password = redisConfig.GetString("password")
globalConfig.Redis.DB = redisConfig.GetInt("db")
}
}
}
// 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)
}
// Try to map from doc_engine section (overrides env var if present)
if v.IsSet("doc_engine") {
docEngineConfig := v.Sub("doc_engine")
if docEngineConfig != nil {
globalConfig.DocEngine.Type = EngineType(docEngineConfig.GetString("type"))
}
}
// Also check legacy es section for backward compatibility
if v.IsSet("es") {
esConfig := v.Sub("es")
if esConfig != nil {
if globalConfig.DocEngine.Type == "" {
globalConfig.DocEngine.Type = EngineElasticsearch
}
if globalConfig.DocEngine.ES == nil {
globalConfig.DocEngine.ES = &ElasticsearchConfig{
Hosts: esConfig.GetString("hosts"),
Username: esConfig.GetString("username"),
Password: esConfig.GetString("password"),
}
}
}
}
if v.IsSet("infinity") {
infConfig := v.Sub("infinity")
if infConfig != nil {
if globalConfig.DocEngine.Type == "" {
globalConfig.DocEngine.Type = EngineInfinity
}
if globalConfig.DocEngine.Infinity == nil {
globalConfig.DocEngine.Infinity = &InfinityConfig{
URI: infConfig.GetString("uri"),
PostgresPort: infConfig.GetInt("postgres_port"),
DBName: infConfig.GetString("db_name"),
}
}
}
}
}
// Map user_default_llm section to UserDefaultLLMConfig
if v.IsSet("user_default_llm") {
userDefaultLLMConfig := v.Sub("user_default_llm")
if userDefaultLLMConfig != nil {
if defaultModels := userDefaultLLMConfig.Sub("default_models"); defaultModels != nil {
globalConfig.UserDefaultLLM.DefaultModels.ChatModel = ModelConfig{
Name: defaultModels.GetString("chat_model.name"),
APIKey: defaultModels.GetString("chat_model.api_key"),
BaseURL: defaultModels.GetString("chat_model.base_url"),
Factory: defaultModels.GetString("chat_model.factory"),
}
globalConfig.UserDefaultLLM.DefaultModels.EmbeddingModel = ModelConfig{
Name: defaultModels.GetString("embedding_model.name"),
APIKey: defaultModels.GetString("embedding_model.api_key"),
BaseURL: defaultModels.GetString("embedding_model.base_url"),
Factory: defaultModels.GetString("embedding_model.factory"),
}
globalConfig.UserDefaultLLM.DefaultModels.RerankModel = ModelConfig{
Name: defaultModels.GetString("rerank_model.name"),
APIKey: defaultModels.GetString("rerank_model.api_key"),
BaseURL: defaultModels.GetString("rerank_model.base_url"),
Factory: defaultModels.GetString("rerank_model.factory"),
}
globalConfig.UserDefaultLLM.DefaultModels.ASRModel = ModelConfig{
Name: defaultModels.GetString("asr_model.name"),
APIKey: defaultModels.GetString("asr_model.api_key"),
BaseURL: defaultModels.GetString("asr_model.base_url"),
Factory: defaultModels.GetString("asr_model.factory"),
}
globalConfig.UserDefaultLLM.DefaultModels.Image2TextModel = ModelConfig{
Name: defaultModels.GetString("image2text_model.name"),
APIKey: defaultModels.GetString("image2text_model.api_key"),
BaseURL: defaultModels.GetString("image2text_model.base_url"),
Factory: defaultModels.GetString("image2text_model.factory"),
}
}
}
}
return nil
}
// Get get global configuration
func GetConfig() *Config {
return globalConfig
}
// GetAdminConfig gets the admin server configuration
func GetAdminConfig() *AdminConfig {
if globalConfig == nil {
return nil
}
return &globalConfig.Admin
}
// SetLogger sets the logger instance
func SetLogger(l *zap.Logger) {
zapLogger = l
}
func GetGlobalViperConfig() *viper.Viper {
return globalViper
}
func GetAllConfigs() []map[string]interface{} {
return allConfigs
}
// PrintAll prints all configuration settings
func PrintAll() {
if globalViper == nil {
zapLogger.Info("Configuration not initialized")
return
}
allSettings := globalViper.AllSettings()
zapLogger.Info("=== All Configuration Settings ===")
for key, value := range allSettings {
zapLogger.Info("config", zap.String("key", key), zap.Any("value", value))
}
zapLogger.Info("=== End Configuration ===")
}
// parseHostPort parses host:port string and returns host and port
func parseHostPort(hostPort string) (string, int) {
if hostPort == "" {
return "", 0
}
// Handle URL format like http://host:port
if strings.Contains(hostPort, "://") {
u, err := url.Parse(hostPort)
if err == nil {
hostPort = u.Host
}
}
// Split host:port
parts := strings.Split(hostPort, ":")
host := parts[0]
port := 0
if len(parts) > 1 {
port, _ = strconv.Atoi(parts[1])
}
return host, port
}
// getString gets string value from map
func getString(m map[string]interface{}, key string) string {
if v, ok := m[key].(string); ok {
return v
}
return ""
}
// getInt gets int value from map
func getInt(m map[string]interface{}, key string) int {
if v, ok := m[key].(int); ok {
return v
}
if v, ok := m[key].(float64); ok {
return int(v)
}
return 0
}