package settings import ( "fmt" "os" "path/filepath" "strconv" "strings" "time" "gopkg.in/ini.v1" "github.com/mayswind/ezbookkeeping/pkg/core" "github.com/mayswind/ezbookkeeping/pkg/errs" "github.com/mayswind/ezbookkeeping/pkg/locales" ) const ( ebkWorkDirEnvName = "EBK_WORK_DIR" ebkConfigItemValueEnvNamePrefix = "EBK" ebkConfigItemFilePathEnvNamePrefix = "EBKCFP" defaultConfigPath = "/conf/ezbookkeeping.ini" defaultRootUrl = "%(protocol)s://%(domain)s:%(http_port)s/" defaultStaticRootPath = "public" ) // SystemMode represents running mode of system type SystemMode string // System running modes const ( MODE_DEVELOPMENT SystemMode = "development" MODE_PRODUCTION SystemMode = "production" ) // Scheme represents how the web backend service exposes type Scheme string // Scheme types const ( SCHEME_HTTP Scheme = "http" SCHEME_HTTPS Scheme = "https" SCHEME_SOCKET Scheme = "socket" ) // Level represents log level type Level string // Log levels const ( LOGLEVEL_DEBUG Level = "debug" LOGLEVEL_INFO Level = "info" LOGLEVEL_WARN Level = "warn" LOGLEVEL_ERROR Level = "error" ) // Database types const ( MySqlDbType string = "mysql" PostgresDbType string = "postgres" Sqlite3DbType string = "sqlite3" ) // Object Storage types const ( LocalFileSystemObjectStorageType string = "local_filesystem" MinIOStorageType string = "minio" WebDAVStorageType string = "webdav" ) const ( OpenAILLMProvider string = "openai" OpenAICompatibleLLMProvider string = "openai_compatible" AnthropicLLMProvider string = "anthropic" AnthropicCompatibleLLMProvider string = "anthropic_compatible" OpenRouterLLMProvider string = "openrouter" OllamaLLMProvider string = "ollama" LMStudioLLMProvider string = "lm_studio" GoogleAILLMProvider string = "google_ai" ) // Uuid generator types const ( InternalUuidGeneratorType string = "internal" ) // Duplicate checker types const ( InMemoryDuplicateCheckerType string = "in_memory" ) // OAuth 2.0 user identifier types const ( OAuth2UserIdentifierEmail string = "email" OAuth2UserIdentifierUsername string = "username" ) // OAuth 2.0 provider types const ( OAuth2ProviderOIDC string = "oidc" OAuth2ProviderNextcloud string = "nextcloud" OAuth2ProviderGitea string = "gitea" OAuth2ProviderGithub string = "github" ) // Map provider types const ( OpenStreetMapProvider string = "openstreetmap" OpenStreetMapHumanitarianStyleProvider string = "openstreetmap_humanitarian" OpenTopoMapProvider string = "opentopomap" OPNVKarteMapProvider string = "opnvkarte" CyclOSMMapProvider string = "cyclosm" CartoDBMapProvider string = "cartodb" TomTomMapProvider string = "tomtom" TianDiTuProvider string = "tianditu" GoogleMapProvider string = "googlemap" BaiduMapProvider string = "baidumap" AmapProvider string = "amap" CustomProvider string = "custom" ) // Amap security verification method const ( AmapSecurityVerificationInternalProxyMethod string = "internal_proxy" AmapSecurityVerificationExternalProxyMethod string = "external_proxy" AmapSecurityVerificationPlainTextMethod string = "plain_text" ) // Exchange rates data source types const ( BankOfCanadaDataSource string = "bank_of_canada" CzechNationalBankDataSource string = "czech_national_bank" DanmarksNationalbankDataSource string = "danmarks_national_bank" EuroCentralBankDataSource string = "euro_central_bank" NationalBankOfGeorgiaDataSource string = "national_bank_of_georgia" CentralBankOfHungaryDataSource string = "central_bank_of_hungary" BankOfIsraelDataSource string = "bank_of_israel" CentralBankOfMyanmarDataSource string = "central_bank_of_myanmar" NorgesBankDataSource string = "norges_bank" NationalBankOfPolandDataSource string = "national_bank_of_poland" NationalBankOfRomaniaDataSource string = "national_bank_of_romania" BankOfRussiaDataSource string = "bank_of_russia" SwissNationalBankDataSource string = "swiss_national_bank" NationalBankOfUkraineDataSource string = "national_bank_of_ukraine" CentralBankOfUzbekistanDataSource string = "central_bank_of_uzbekistan" UserCustomExchangeRatesDataSource string = "user_custom" ) const ( defaultHttpAddr string = "0.0.0.0" defaultHttpPort uint16 = 8080 defaultDomain string = "localhost" defaultDatabaseHost string = "127.0.0.1:3306" defaultDatabaseName string = "ezbookkeeping" defaultDatabaseMaxIdleConn uint16 = 2 defaultDatabaseMaxOpenConn uint16 = 0 defaultDatabaseConnMaxLifetime uint32 = 14400 defaultLogMode string = "console" defaultLogFileMaxSize uint32 = 104857600 // 100 MB defaultLogFileMaxDays uint32 = 7 // days defaultWebDAVRequestTimeout uint32 = 10000 // 10 seconds defaultAIRecognitionPictureMaxSize uint32 = 10485760 // 10MB defaultAnthropicLargeLanguageModelAPIMaximumTokens uint32 = 1024 defaultLargeLanguageModelAPIRequestTimeout uint32 = 60000 // 60 seconds defaultInMemoryDuplicateCheckerCleanupInterval uint32 = 60 // 1 minutes defaultDuplicateSubmissionsInterval uint32 = 300 // 5 minutes defaultSecretKey string = "ezbookkeeping" defaultTokenExpiredTime uint32 = 2592000 // 30 days defaultTokenMinRefreshInterval uint32 = 86400 // 1 day defaultTemporaryTokenExpiredTime uint32 = 300 // 5 minutes defaultEmailVerifyTokenExpiredTime uint32 = 3600 // 60 minutes defaultPasswordResetTokenExpiredTime uint32 = 3600 // 60 minutes defaultMaxFailuresPerIpPerMinute uint32 = 5 defaultMaxFailuresPerUserPerMinute uint32 = 5 defaultOAuth2StateExpiredTime uint32 = 300 // 5 minutes defaultOAuth2RequestTimeout uint32 = 10000 // 10 seconds defaultTransactionPictureFileMaxSize uint32 = 10485760 // 10MB defaultUserAvatarFileMaxSize uint32 = 1048576 // 1MB defaultImportFileMaxSize uint32 = 10485760 // 10MB defaultExchangeRatesDataRequestTimeout uint32 = 10000 // 10 seconds ) // DatabaseConfig represents the database setting config type DatabaseConfig struct { DatabaseType string DatabaseHost string DatabaseName string DatabaseUser string DatabasePassword string DatabaseSSLMode string DatabasePath string MaxIdleConnection uint16 MaxOpenConnection uint16 ConnectionMaxLifeTime uint32 } // SMTPConfig represents the SMTP setting config type SMTPConfig struct { SMTPHost string SMTPUser string SMTPPasswd string SMTPSkipTLSVerify bool FromAddress string } // MinIOConfig represents the MinIO setting config type MinIOConfig struct { Endpoint string Location string AccessKeyID string SecretAccessKey string UseSSL bool SkipTLSVerify bool Bucket string RootPath string } // WebDAVConfig represents the WebDAV setting config type WebDAVConfig struct { Url string Username string Password string RootPath string RequestTimeout uint32 Proxy string SkipTLSVerify bool } // LLMConfig represents the Large Language Model setting config type LLMConfig struct { LLMProvider string OpenAIAPIKey string OpenAIModelID string OpenAICompatibleBaseURL string OpenAICompatibleAPIKey string OpenAICompatibleModelID string AnthropicAPIKey string AnthropicModelID string AnthropicMaxTokens uint32 AnthropicCompatibleBaseURL string AnthropicCompatibleAPIVersion string AnthropicCompatibleAPIKey string AnthropicCompatibleModelID string AnthropicCompatibleMaxTokens uint32 OpenRouterAPIKey string OpenRouterModelID string OllamaServerURL string OllamaModelID string LMStudioServerURL string LMStudioToken string LMStudioModelID string GoogleAIAPIKey string GoogleAIModelID string LargeLanguageModelAPIRequestTimeout uint32 LargeLanguageModelAPIProxy string LargeLanguageModelAPISkipTLSVerify bool } // MultiLanguageContentConfig represents a multi-language content setting config type MultiLanguageContentConfig struct { Enabled bool DefaultContent string MultiLanguageContent map[string]string } // Config represents the global setting config type Config struct { // Global Mode SystemMode WorkingPath string // Server Protocol Scheme HttpAddr string HttpPort uint16 Domain string RootUrl string CertFile string CertKeyFile string UnixSocketPath string StaticRootPath string EnableGZip bool EnableRequestLog bool EnableRequestIdHeader bool // MCP EnableMCPServer bool MCPAllowedRemoteIPs []*core.IPPattern // Database DatabaseConfig *DatabaseConfig EnableQueryLog bool AutoUpdateDatabase bool // Mail EnableSMTP bool SMTPConfig *SMTPConfig // Log LogModes []string EnableConsoleLog bool EnableFileLog bool EnableDebugLog bool LogLevel Level FileLogPath string RequestFileLogPath string QueryFileLogPath string LogFileRotate bool LogFileMaxSize uint32 LogFileMaxDays uint32 // Storage StorageType string LocalFileSystemPath string MinIOConfig *MinIOConfig WebDAVConfig *WebDAVConfig // Large Language Model TransactionFromAIImageRecognition bool MaxAIRecognitionPictureFileSize uint32 // Large Language Model for Receipt Image Recognition ReceiptImageRecognitionLLMConfig *LLMConfig // Uuid UuidGeneratorType string UuidServerId uint8 // Duplicate Checker DuplicateCheckerType string InMemoryDuplicateCheckerCleanupInterval uint32 InMemoryDuplicateCheckerCleanupIntervalDuration time.Duration EnableDuplicateSubmissionsCheck bool DuplicateSubmissionsInterval uint32 DuplicateSubmissionsIntervalDuration time.Duration // Cron EnableRemoveExpiredTokens bool EnableCreateScheduledTransaction bool // Secret SecretKeyNoSet bool SecretKey string TokenExpiredTime uint32 TokenExpiredTimeDuration time.Duration TokenMinRefreshInterval uint32 TemporaryTokenExpiredTime uint32 TemporaryTokenExpiredTimeDuration time.Duration EmailVerifyTokenExpiredTime uint32 EmailVerifyTokenExpiredTimeDuration time.Duration PasswordResetTokenExpiredTime uint32 PasswordResetTokenExpiredTimeDuration time.Duration EnableAPIToken bool MaxFailuresPerIpPerMinute uint32 MaxFailuresPerUserPerMinute uint32 // Auth EnableInternalAuth bool EnableOAuth2Login bool EnableTwoFactor bool EnableUserForgetPassword bool ForgetPasswordRequireVerifyEmail bool OAuth2ClientID string OAuth2ClientSecret string OAuth2UsePKCE bool OAuth2UserIdentifier string OAuth2AutoRegister bool OAuth2Provider string OAuth2StateExpiredTime uint32 OAuth2StateExpiredTimeDuration time.Duration OAuth2RequestTimeout uint32 OAuth2Proxy string OAuth2SkipTLSVerify bool OAuth2OIDCProviderIssuerURL string OAuth2OIDCProviderCheckIssuerURL bool OAuth2OIDCCustomDisplayNameConfig MultiLanguageContentConfig OAuth2NextcloudBaseUrl string OAuth2GiteaBaseUrl string // User EnableUserRegister bool EnableUserVerifyEmail bool EnableUserForceVerifyEmail bool EnableTransactionPictures bool MaxTransactionPictureFileSize uint32 EnableScheduledTransaction bool AvatarProvider core.UserAvatarProviderType MaxAvatarFileSize uint32 DefaultFeatureRestrictions core.UserFeatureRestrictions // Data EnableDataExport bool EnableDataImport bool MaxImportFileSize uint32 // Tip LoginPageTips MultiLanguageContentConfig // Notification AfterRegisterNotification MultiLanguageContentConfig AfterLoginNotification MultiLanguageContentConfig AfterOpenNotification MultiLanguageContentConfig // Map MapProvider string EnableMapDataFetchProxy bool MapProxy string TomTomMapAPIKey string TianDiTuAPIKey string GoogleMapAPIKey string BaiduMapAK string AmapApplicationKey string AmapSecurityVerificationMethod string AmapApplicationSecret string AmapApiExternalProxyUrl string CustomMapTileServerTileLayerUrl string CustomMapTileServerAnnotationLayerUrl string CustomMapTileServerMinZoomLevel uint8 CustomMapTileServerMaxZoomLevel uint8 CustomMapTileServerDefaultZoomLevel uint8 // Exchange Rates ExchangeRatesDataSource string ExchangeRatesRequestTimeout uint32 ExchangeRatesRequestTimeoutExceedDefaultValue bool ExchangeRatesProxy string ExchangeRatesSkipTLSVerify bool } // LoadConfiguration loads setting config from given config file path func LoadConfiguration(configFilePath string) (*Config, error) { var err error cfgFile, err := ini.LoadSources(ini.LoadOptions{ IgnoreInlineComment: true, }, configFilePath) if err != nil { return nil, err } config := &Config{} config.WorkingPath, err = getWorkingPath() if err != nil { return nil, err } err = loadGlobalConfiguration(config, cfgFile, "global") if err != nil { return nil, err } err = loadServerConfiguration(config, cfgFile, "server") if err != nil { return nil, err } err = loadMCPServerConfiguration(config, cfgFile, "mcp") if err != nil { return nil, err } err = loadDatabaseConfiguration(config, cfgFile, "database") if err != nil { return nil, err } err = loadMailConfiguration(config, cfgFile, "mail") if err != nil { return nil, err } err = loadLogConfiguration(config, cfgFile, "log") if err != nil { return nil, err } err = loadStorageConfiguration(config, cfgFile, "storage") if err != nil { return nil, err } err = loadLLMGlobalConfiguration(config, cfgFile, "llm") if err != nil { return nil, err } config.ReceiptImageRecognitionLLMConfig, err = loadLLMConfiguration(cfgFile, "llm_image_recognition") if err != nil { return nil, err } err = loadUuidConfiguration(config, cfgFile, "uuid") if err != nil { return nil, err } err = loadDuplicateCheckerConfiguration(config, cfgFile, "duplicate_checker") if err != nil { return nil, err } err = loadCronConfiguration(config, cfgFile, "cron") if err != nil { return nil, err } err = loadSecurityConfiguration(config, cfgFile, "security") if err != nil { return nil, err } err = loadAuthConfiguration(config, cfgFile, "auth") if err != nil { return nil, err } err = loadUserConfiguration(config, cfgFile, "user") if err != nil { return nil, err } err = loadDataConfiguration(config, cfgFile, "data") if err != nil { return nil, err } err = loadTipConfiguration(config, cfgFile, "tip") if err != nil { return nil, err } err = loadNotificationConfiguration(config, cfgFile, "notification") if err != nil { return nil, err } err = loadMapConfiguration(config, cfgFile, "map") if err != nil { return nil, err } err = loadExchangeRatesConfiguration(config, cfgFile, "exchange_rates") if err != nil { return nil, err } return config, nil } // GetDefaultConfigFilePath returns the defaule config file path func GetDefaultConfigFilePath() (string, error) { workingPath, err := getWorkingPath() if err != nil { return "", err } cfgFilePath := filepath.Join(workingPath, defaultConfigPath) _, err = os.Stat(cfgFilePath) if err != nil { return "", err } return cfgFilePath, nil } func loadGlobalConfiguration(config *Config, configFile *ini.File, sectionName string) error { if getConfigItemStringValue(configFile, sectionName, "mode") == "production" { config.Mode = MODE_PRODUCTION } else if getConfigItemStringValue(configFile, sectionName, "mode") == "development" { config.Mode = MODE_DEVELOPMENT } else { return errs.ErrInvalidServerMode } return nil } func loadServerConfiguration(config *Config, configFile *ini.File, sectionName string) error { if getConfigItemStringValue(configFile, sectionName, "protocol") == "http" { config.Protocol = SCHEME_HTTP config.HttpAddr = getConfigItemStringValue(configFile, sectionName, "http_addr", defaultHttpAddr) config.HttpPort = getConfigItemUint16Value(configFile, sectionName, "http_port", defaultHttpPort) } else if getConfigItemStringValue(configFile, sectionName, "protocol") == "https" { config.Protocol = SCHEME_HTTPS config.HttpAddr = getConfigItemStringValue(configFile, sectionName, "http_addr", defaultHttpAddr) config.HttpPort = getConfigItemUint16Value(configFile, sectionName, "http_port", defaultHttpPort) config.CertFile = getConfigItemStringValue(configFile, sectionName, "cert_file") config.CertKeyFile = getConfigItemStringValue(configFile, sectionName, "cert_key_file") } else if getConfigItemStringValue(configFile, sectionName, "protocol") == "socket" { config.Protocol = SCHEME_SOCKET config.UnixSocketPath = getConfigItemStringValue(configFile, sectionName, "unix_socket") } else { return errs.ErrInvalidProtocol } config.Domain = getConfigItemStringValue(configFile, sectionName, "domain", defaultDomain) config.RootUrl = getConfigItemStringValue(configFile, sectionName, "root_url", defaultRootUrl) config.RootUrl = strings.ReplaceAll(config.RootUrl, "%(protocol)s", string(config.Protocol)) config.RootUrl = strings.ReplaceAll(config.RootUrl, "%(domain)s", config.Domain) config.RootUrl = strings.ReplaceAll(config.RootUrl, "%(http_port)s", strconv.Itoa(int(config.HttpPort))) if config.RootUrl[len(config.RootUrl)-1] != '/' { config.RootUrl += "/" } staticRootPath := getConfigItemStringValue(configFile, sectionName, "static_root_path", defaultStaticRootPath) finalStaticRootPath, err := getFinalPath(config.WorkingPath, staticRootPath) if err != nil { return err } config.StaticRootPath = finalStaticRootPath config.EnableGZip = getConfigItemBoolValue(configFile, sectionName, "enable_gzip", false) config.EnableRequestLog = getConfigItemBoolValue(configFile, sectionName, "log_request", false) config.EnableRequestIdHeader = getConfigItemBoolValue(configFile, sectionName, "request_id_header", true) return nil } func loadMCPServerConfiguration(config *Config, configFile *ini.File, sectionName string) error { config.EnableMCPServer = getConfigItemBoolValue(configFile, sectionName, "enable_mcp", false) mcpAllowedRemoteIps := getConfigItemStringValue(configFile, sectionName, "mcp_allowed_remote_ips", "") if mcpAllowedRemoteIps != "" { remoteIPs := strings.Split(mcpAllowedRemoteIps, ",") config.MCPAllowedRemoteIPs = make([]*core.IPPattern, 0, len(remoteIPs)) for i := 0; i < len(remoteIPs); i++ { ip := strings.TrimSpace(remoteIPs[i]) pattern, err := core.ParseIPPattern(ip) if err != nil { return err } if pattern == nil { continue } config.MCPAllowedRemoteIPs = append(config.MCPAllowedRemoteIPs, pattern) } } else { config.MCPAllowedRemoteIPs = nil } return nil } func loadDatabaseConfiguration(config *Config, configFile *ini.File, sectionName string) error { dbConfig := &DatabaseConfig{} dbConfig.DatabaseType = getConfigItemStringValue(configFile, sectionName, "type", MySqlDbType) if dbConfig.DatabaseType != MySqlDbType && dbConfig.DatabaseType != PostgresDbType && dbConfig.DatabaseType != Sqlite3DbType { return errs.ErrDatabaseTypeInvalid } dbConfig.DatabaseHost = getConfigItemStringValue(configFile, sectionName, "host", defaultDatabaseHost) dbConfig.DatabaseName = getConfigItemStringValue(configFile, sectionName, "name", defaultDatabaseName) dbConfig.DatabaseUser = getConfigItemStringValue(configFile, sectionName, "user") dbConfig.DatabasePassword = getConfigItemStringValue(configFile, sectionName, "passwd") if dbConfig.DatabaseType == PostgresDbType { dbConfig.DatabaseSSLMode = getConfigItemStringValue(configFile, sectionName, "ssl_mode") } if dbConfig.DatabaseType == Sqlite3DbType { staticDBPath := getConfigItemStringValue(configFile, sectionName, "db_path") finalStaticDBPath, _ := getFinalPath(config.WorkingPath, staticDBPath) dbConfig.DatabasePath = finalStaticDBPath } dbConfig.MaxIdleConnection = getConfigItemUint16Value(configFile, sectionName, "max_idle_conn", defaultDatabaseMaxIdleConn) dbConfig.MaxOpenConnection = getConfigItemUint16Value(configFile, sectionName, "max_open_conn", defaultDatabaseMaxOpenConn) dbConfig.ConnectionMaxLifeTime = getConfigItemUint32Value(configFile, sectionName, "conn_max_lifetime", defaultDatabaseConnMaxLifetime) config.DatabaseConfig = dbConfig config.EnableQueryLog = getConfigItemBoolValue(configFile, sectionName, "log_query", false) config.AutoUpdateDatabase = getConfigItemBoolValue(configFile, sectionName, "auto_update_database", true) return nil } func loadMailConfiguration(config *Config, configFile *ini.File, sectionName string) error { config.EnableSMTP = getConfigItemBoolValue(configFile, sectionName, "enable_smtp", false) smtpConfig := &SMTPConfig{} smtpConfig.SMTPHost = getConfigItemStringValue(configFile, sectionName, "smtp_host") smtpConfig.SMTPUser = getConfigItemStringValue(configFile, sectionName, "smtp_user") smtpConfig.SMTPPasswd = getConfigItemStringValue(configFile, sectionName, "smtp_passwd") smtpConfig.SMTPSkipTLSVerify = getConfigItemBoolValue(configFile, sectionName, "smtp_skip_tls_verify", false) smtpConfig.FromAddress = getConfigItemStringValue(configFile, sectionName, "from_address") config.SMTPConfig = smtpConfig return nil } func loadLogConfiguration(config *Config, configFile *ini.File, sectionName string) error { config.LogModes = strings.Split(getConfigItemStringValue(configFile, sectionName, "mode", defaultLogMode), " ") for i := 0; i < len(config.LogModes); i++ { logMode := config.LogModes[i] if logMode == "console" { config.EnableConsoleLog = true } else if logMode == "file" { config.EnableFileLog = true } else { return errs.ErrInvalidLogMode } } var err error config.LogLevel, err = getLogLevel(getConfigItemStringValue(configFile, sectionName, "level")) if err != nil { return err } if config.LogLevel != LOGLEVEL_DEBUG && config.LogLevel != LOGLEVEL_INFO && config.LogLevel != LOGLEVEL_WARN && config.LogLevel != LOGLEVEL_ERROR { return errs.ErrInvalidLogLevel } if config.LogLevel == LOGLEVEL_DEBUG { config.EnableDebugLog = true } else { config.EnableDebugLog = false } if config.EnableFileLog { fileLogPath := getConfigItemStringValue(configFile, sectionName, "log_path") finalFileLogPath, _ := getFinalPath(config.WorkingPath, fileLogPath) config.FileLogPath = finalFileLogPath requestFileLogPath := getConfigItemStringValue(configFile, sectionName, "request_log_path") if requestFileLogPath != "" { finalRequestFileLogPath, _ := getFinalPath(config.WorkingPath, requestFileLogPath) config.RequestFileLogPath = finalRequestFileLogPath } else { config.RequestFileLogPath = "" } queryFileLogPath := getConfigItemStringValue(configFile, sectionName, "query_log_path") if queryFileLogPath != "" { finalQueryFileLogPath, _ := getFinalPath(config.WorkingPath, queryFileLogPath) config.QueryFileLogPath = finalQueryFileLogPath } else { config.QueryFileLogPath = "" } config.LogFileRotate = getConfigItemBoolValue(configFile, sectionName, "log_file_rotate", false) config.LogFileMaxSize = getConfigItemUint32Value(configFile, sectionName, "log_file_max_size", defaultLogFileMaxSize) config.LogFileMaxDays = getConfigItemUint32Value(configFile, sectionName, "log_file_max_days", defaultLogFileMaxDays) } return nil } func loadStorageConfiguration(config *Config, configFile *ini.File, sectionName string) error { if getConfigItemStringValue(configFile, sectionName, "type") == LocalFileSystemObjectStorageType { config.StorageType = LocalFileSystemObjectStorageType } else if getConfigItemStringValue(configFile, sectionName, "type") == MinIOStorageType { config.StorageType = MinIOStorageType } else if getConfigItemStringValue(configFile, sectionName, "type") == WebDAVStorageType { config.StorageType = WebDAVStorageType } else { return errs.ErrInvalidStorageType } localFileSystemRootPath := getConfigItemStringValue(configFile, sectionName, "local_filesystem_path") finalLocalFileSystemRootPath, err := getFinalPath(config.WorkingPath, localFileSystemRootPath) config.LocalFileSystemPath = finalLocalFileSystemRootPath if config.StorageType == LocalFileSystemObjectStorageType && err != nil { return errs.ErrInvalidLocalFileSystemStoragePath } minIOConfig := &MinIOConfig{} minIOConfig.Endpoint = getConfigItemStringValue(configFile, sectionName, "minio_endpoint") minIOConfig.Location = getConfigItemStringValue(configFile, sectionName, "minio_location") minIOConfig.AccessKeyID = getConfigItemStringValue(configFile, sectionName, "minio_access_key_id") minIOConfig.SecretAccessKey = getConfigItemStringValue(configFile, sectionName, "minio_secret_access_key") minIOConfig.UseSSL = getConfigItemBoolValue(configFile, sectionName, "minio_use_ssl", false) minIOConfig.SkipTLSVerify = getConfigItemBoolValue(configFile, sectionName, "minio_skip_tls_verify", false) minIOConfig.Bucket = getConfigItemStringValue(configFile, sectionName, "minio_bucket") minIOConfig.RootPath = getConfigItemStringValue(configFile, sectionName, "minio_root_path") config.MinIOConfig = minIOConfig webDAVConfig := &WebDAVConfig{} webDAVConfig.Url = getConfigItemStringValue(configFile, sectionName, "webdav_url") webDAVConfig.Username = getConfigItemStringValue(configFile, sectionName, "webdav_username") webDAVConfig.Password = getConfigItemStringValue(configFile, sectionName, "webdav_password") webDAVConfig.RootPath = getConfigItemStringValue(configFile, sectionName, "webdav_root_path") webDAVConfig.RequestTimeout = getConfigItemUint32Value(configFile, sectionName, "webdav_request_timeout", defaultWebDAVRequestTimeout) webDAVConfig.Proxy = getConfigItemStringValue(configFile, sectionName, "webdav_proxy", "system") webDAVConfig.SkipTLSVerify = getConfigItemBoolValue(configFile, sectionName, "webdav_skip_tls_verify", false) config.WebDAVConfig = webDAVConfig return nil } func loadLLMGlobalConfiguration(config *Config, configFile *ini.File, sectionName string) error { config.TransactionFromAIImageRecognition = getConfigItemBoolValue(configFile, sectionName, "transaction_from_ai_image_recognition", false) config.MaxAIRecognitionPictureFileSize = getConfigItemUint32Value(configFile, sectionName, "max_ai_recognition_picture_size", defaultAIRecognitionPictureMaxSize) return nil } func loadLLMConfiguration(configFile *ini.File, sectionName string) (*LLMConfig, error) { llmConfig := &LLMConfig{} llmProvider := getConfigItemStringValue(configFile, sectionName, "llm_provider") if llmProvider == "" { llmConfig.LLMProvider = "" } else if llmProvider == OpenAILLMProvider { llmConfig.LLMProvider = OpenAILLMProvider } else if llmProvider == OpenAICompatibleLLMProvider { llmConfig.LLMProvider = OpenAICompatibleLLMProvider } else if llmProvider == AnthropicLLMProvider { llmConfig.LLMProvider = AnthropicLLMProvider } else if llmProvider == AnthropicCompatibleLLMProvider { llmConfig.LLMProvider = AnthropicCompatibleLLMProvider } else if llmProvider == OpenRouterLLMProvider { llmConfig.LLMProvider = OpenRouterLLMProvider } else if llmProvider == OllamaLLMProvider { llmConfig.LLMProvider = OllamaLLMProvider } else if llmProvider == LMStudioLLMProvider { llmConfig.LLMProvider = LMStudioLLMProvider } else if llmProvider == GoogleAILLMProvider { llmConfig.LLMProvider = GoogleAILLMProvider } else { return nil, errs.ErrInvalidLLMProvider } llmConfig.OpenAIAPIKey = getConfigItemStringValue(configFile, sectionName, "openai_api_key") llmConfig.OpenAIModelID = getConfigItemStringValue(configFile, sectionName, "openai_model_id") llmConfig.OpenAICompatibleBaseURL = getConfigItemStringValue(configFile, sectionName, "openai_compatible_base_url") llmConfig.OpenAICompatibleAPIKey = getConfigItemStringValue(configFile, sectionName, "openai_compatible_api_key") llmConfig.OpenAICompatibleModelID = getConfigItemStringValue(configFile, sectionName, "openai_compatible_model_id") llmConfig.AnthropicAPIKey = getConfigItemStringValue(configFile, sectionName, "anthropic_api_key") llmConfig.AnthropicModelID = getConfigItemStringValue(configFile, sectionName, "anthropic_model_id") llmConfig.AnthropicMaxTokens = getConfigItemUint32Value(configFile, sectionName, "anthropic_max_tokens", defaultAnthropicLargeLanguageModelAPIMaximumTokens) llmConfig.AnthropicCompatibleBaseURL = getConfigItemStringValue(configFile, sectionName, "anthropic_compatible_base_url") llmConfig.AnthropicCompatibleAPIVersion = getConfigItemStringValue(configFile, sectionName, "anthropic_compatible_api_version") llmConfig.AnthropicCompatibleAPIKey = getConfigItemStringValue(configFile, sectionName, "anthropic_compatible_api_key") llmConfig.AnthropicCompatibleModelID = getConfigItemStringValue(configFile, sectionName, "anthropic_compatible_model_id") llmConfig.AnthropicCompatibleMaxTokens = getConfigItemUint32Value(configFile, sectionName, "anthropic_compatible_max_tokens", defaultAnthropicLargeLanguageModelAPIMaximumTokens) llmConfig.OpenRouterAPIKey = getConfigItemStringValue(configFile, sectionName, "openrouter_api_key") llmConfig.OpenRouterModelID = getConfigItemStringValue(configFile, sectionName, "openrouter_model_id") llmConfig.OllamaServerURL = getConfigItemStringValue(configFile, sectionName, "ollama_server_url") llmConfig.OllamaModelID = getConfigItemStringValue(configFile, sectionName, "ollama_model_id") llmConfig.LMStudioServerURL = getConfigItemStringValue(configFile, sectionName, "lm_studio_server_url") llmConfig.LMStudioToken = getConfigItemStringValue(configFile, sectionName, "lm_studio_token") llmConfig.LMStudioModelID = getConfigItemStringValue(configFile, sectionName, "lm_studio_model_id") llmConfig.GoogleAIAPIKey = getConfigItemStringValue(configFile, sectionName, "google_ai_api_key") llmConfig.GoogleAIModelID = getConfigItemStringValue(configFile, sectionName, "google_ai_model_id") llmConfig.LargeLanguageModelAPIProxy = getConfigItemStringValue(configFile, sectionName, "proxy", "system") llmConfig.LargeLanguageModelAPIRequestTimeout = getConfigItemUint32Value(configFile, sectionName, "request_timeout", defaultLargeLanguageModelAPIRequestTimeout) llmConfig.LargeLanguageModelAPISkipTLSVerify = getConfigItemBoolValue(configFile, sectionName, "skip_tls_verify", false) return llmConfig, nil } func loadUuidConfiguration(config *Config, configFile *ini.File, sectionName string) error { if getConfigItemStringValue(configFile, sectionName, "generator_type") == InternalUuidGeneratorType { config.UuidGeneratorType = InternalUuidGeneratorType } else { return errs.ErrInvalidUuidMode } config.UuidServerId = getConfigItemUint8Value(configFile, sectionName, "server_id", 0) return nil } func loadDuplicateCheckerConfiguration(config *Config, configFile *ini.File, sectionName string) error { if getConfigItemStringValue(configFile, sectionName, "checker_type") == InMemoryDuplicateCheckerType { config.DuplicateCheckerType = InMemoryDuplicateCheckerType } else { return errs.ErrInvalidDuplicateCheckerType } config.InMemoryDuplicateCheckerCleanupInterval = getConfigItemUint32Value(configFile, sectionName, "cleanup_interval", defaultInMemoryDuplicateCheckerCleanupInterval) if config.InMemoryDuplicateCheckerCleanupInterval < 1 { return errs.ErrInvalidInMemoryDuplicateCheckerCleanupInterval } config.InMemoryDuplicateCheckerCleanupIntervalDuration = time.Duration(config.InMemoryDuplicateCheckerCleanupInterval) * time.Second duplicateSubmissionsInterval := getConfigItemUint32Value(configFile, sectionName, "duplicate_submissions_interval", defaultDuplicateSubmissionsInterval) config.EnableDuplicateSubmissionsCheck = duplicateSubmissionsInterval > 0 if duplicateSubmissionsInterval < 1 { duplicateSubmissionsInterval = defaultDuplicateSubmissionsInterval } config.DuplicateSubmissionsInterval = duplicateSubmissionsInterval config.DuplicateSubmissionsIntervalDuration = time.Duration(config.DuplicateSubmissionsInterval) * time.Second return nil } func loadCronConfiguration(config *Config, configFile *ini.File, sectionName string) error { config.EnableRemoveExpiredTokens = getConfigItemBoolValue(configFile, sectionName, "enable_remove_expired_tokens", false) config.EnableCreateScheduledTransaction = getConfigItemBoolValue(configFile, sectionName, "enable_create_scheduled_transaction", false) return nil } func loadSecurityConfiguration(config *Config, configFile *ini.File, sectionName string) error { config.SecretKeyNoSet = !getConfigItemIsSet(configFile, sectionName, "secret_key") config.SecretKey = getConfigItemStringValue(configFile, sectionName, "secret_key", defaultSecretKey) config.TokenExpiredTime = getConfigItemUint32Value(configFile, sectionName, "token_expired_time", defaultTokenExpiredTime) if config.TokenExpiredTime < 60 { return errs.ErrInvalidTokenExpiredTime } config.TokenExpiredTimeDuration = time.Duration(config.TokenExpiredTime) * time.Second config.TokenMinRefreshInterval = getConfigItemUint32Value(configFile, sectionName, "token_min_refresh_interval", defaultTokenMinRefreshInterval) if config.TokenMinRefreshInterval >= config.TokenExpiredTime { return errs.ErrInvalidTokenMinRefreshInterval } config.TemporaryTokenExpiredTime = getConfigItemUint32Value(configFile, sectionName, "temporary_token_expired_time", defaultTemporaryTokenExpiredTime) if config.TemporaryTokenExpiredTime < 60 { return errs.ErrInvalidTemporaryTokenExpiredTime } config.TemporaryTokenExpiredTimeDuration = time.Duration(config.TemporaryTokenExpiredTime) * time.Second config.EmailVerifyTokenExpiredTime = getConfigItemUint32Value(configFile, sectionName, "email_verify_token_expired_time", defaultEmailVerifyTokenExpiredTime) if config.EmailVerifyTokenExpiredTime < 60 { return errs.ErrInvalidEmailVerifyTokenExpiredTime } config.EmailVerifyTokenExpiredTimeDuration = time.Duration(config.EmailVerifyTokenExpiredTime) * time.Second config.PasswordResetTokenExpiredTime = getConfigItemUint32Value(configFile, sectionName, "password_reset_token_expired_time", defaultPasswordResetTokenExpiredTime) if config.PasswordResetTokenExpiredTime < 60 { return errs.ErrInvalidPasswordResetTokenExpiredTime } config.PasswordResetTokenExpiredTimeDuration = time.Duration(config.PasswordResetTokenExpiredTime) * time.Second config.EnableAPIToken = getConfigItemBoolValue(configFile, sectionName, "enable_api_token", false) config.MaxFailuresPerIpPerMinute = getConfigItemUint32Value(configFile, sectionName, "max_failures_per_ip_per_minute", defaultMaxFailuresPerIpPerMinute) config.MaxFailuresPerUserPerMinute = getConfigItemUint32Value(configFile, sectionName, "max_failures_per_user_per_minute", defaultMaxFailuresPerUserPerMinute) return nil } func loadAuthConfiguration(config *Config, configFile *ini.File, sectionName string) error { config.EnableInternalAuth = getConfigItemBoolValue(configFile, sectionName, "enable_internal_auth", true) config.EnableOAuth2Login = getConfigItemBoolValue(configFile, sectionName, "enable_oauth2_auth", false) config.EnableTwoFactor = getConfigItemBoolValue(configFile, sectionName, "enable_two_factor", true) config.EnableUserForgetPassword = getConfigItemBoolValue(configFile, sectionName, "enable_forget_password", false) config.ForgetPasswordRequireVerifyEmail = getConfigItemBoolValue(configFile, sectionName, "forget_password_require_email_verify", false) config.OAuth2ClientID = getConfigItemStringValue(configFile, sectionName, "oauth2_client_id") config.OAuth2ClientSecret = getConfigItemStringValue(configFile, sectionName, "oauth2_client_secret") config.OAuth2UsePKCE = getConfigItemBoolValue(configFile, sectionName, "oauth2_use_pkce", false) oauth2UserIdentifier := getConfigItemStringValue(configFile, sectionName, "oauth2_user_identifier") if oauth2UserIdentifier == OAuth2UserIdentifierEmail { config.OAuth2UserIdentifier = OAuth2UserIdentifierEmail } else if oauth2UserIdentifier == OAuth2UserIdentifierUsername { config.OAuth2UserIdentifier = OAuth2UserIdentifierUsername } else { return errs.ErrInvalidOAuth2UserIdentifier } config.OAuth2AutoRegister = getConfigItemBoolValue(configFile, sectionName, "oauth2_auto_register", true) oauth2Provider := getConfigItemStringValue(configFile, sectionName, "oauth2_provider") if oauth2Provider == "" { config.OAuth2Provider = "" } else if oauth2Provider == OAuth2ProviderOIDC { config.OAuth2Provider = OAuth2ProviderOIDC } else if oauth2Provider == OAuth2ProviderNextcloud { config.OAuth2Provider = OAuth2ProviderNextcloud } else if oauth2Provider == OAuth2ProviderGitea { config.OAuth2Provider = OAuth2ProviderGitea } else if oauth2Provider == OAuth2ProviderGithub { config.OAuth2Provider = OAuth2ProviderGithub } else { return errs.ErrInvalidOAuth2Provider } config.OAuth2StateExpiredTime = getConfigItemUint32Value(configFile, sectionName, "oauth2_state_expired_time", defaultOAuth2StateExpiredTime) if config.OAuth2StateExpiredTime < 60 { return errs.ErrInvalidOAuth2StateExpiredTime } config.OAuth2StateExpiredTimeDuration = time.Duration(config.OAuth2StateExpiredTime) * time.Second config.OAuth2Proxy = getConfigItemStringValue(configFile, sectionName, "oauth2_proxy", "system") config.OAuth2RequestTimeout = getConfigItemUint32Value(configFile, sectionName, "oauth2_request_timeout", defaultOAuth2RequestTimeout) config.OAuth2SkipTLSVerify = getConfigItemBoolValue(configFile, sectionName, "oauth2_skip_tls_verify", false) config.OAuth2OIDCProviderIssuerURL = getConfigItemStringValue(configFile, sectionName, "oidc_provider_base_url") config.OAuth2OIDCProviderCheckIssuerURL = getConfigItemBoolValue(configFile, sectionName, "oidc_provider_check_issuer_url", true) config.OAuth2OIDCCustomDisplayNameConfig = getMultiLanguageContentConfig(configFile, sectionName, "enable_oidc_display_name", "oidc_custom_display_name") config.OAuth2NextcloudBaseUrl = getConfigItemStringValue(configFile, sectionName, "nextcloud_base_url") config.OAuth2GiteaBaseUrl = getConfigItemStringValue(configFile, sectionName, "gitea_base_url") return nil } func loadUserConfiguration(config *Config, configFile *ini.File, sectionName string) error { config.EnableUserRegister = getConfigItemBoolValue(configFile, sectionName, "enable_register", false) config.EnableUserVerifyEmail = getConfigItemBoolValue(configFile, sectionName, "enable_email_verify", false) config.EnableUserForceVerifyEmail = getConfigItemBoolValue(configFile, sectionName, "enable_force_email_verify", false) config.EnableTransactionPictures = getConfigItemBoolValue(configFile, sectionName, "enable_transaction_picture", false) config.MaxTransactionPictureFileSize = getConfigItemUint32Value(configFile, sectionName, "max_transaction_picture_size", defaultTransactionPictureFileMaxSize) config.EnableScheduledTransaction = getConfigItemBoolValue(configFile, sectionName, "enable_scheduled_transaction", false) if getConfigItemStringValue(configFile, sectionName, "avatar_provider") == string(core.USER_AVATAR_PROVIDER_INTERNAL) { config.AvatarProvider = core.USER_AVATAR_PROVIDER_INTERNAL } else if getConfigItemStringValue(configFile, sectionName, "avatar_provider") == string(core.USER_AVATAR_PROVIDER_GRAVATAR) { config.AvatarProvider = core.USER_AVATAR_PROVIDER_GRAVATAR } else if getConfigItemStringValue(configFile, sectionName, "avatar_provider") == "" { config.AvatarProvider = "" } else { return errs.ErrInvalidAvatarProvider } config.MaxAvatarFileSize = getConfigItemUint32Value(configFile, sectionName, "max_user_avatar_size", defaultUserAvatarFileMaxSize) config.DefaultFeatureRestrictions = core.ParseUserFeatureRestrictions(getConfigItemStringValue(configFile, sectionName, "default_feature_restrictions", "")) return nil } func loadDataConfiguration(config *Config, configFile *ini.File, sectionName string) error { config.EnableDataExport = getConfigItemBoolValue(configFile, sectionName, "enable_export", false) config.EnableDataImport = getConfigItemBoolValue(configFile, sectionName, "enable_import", false) config.MaxImportFileSize = getConfigItemUint32Value(configFile, sectionName, "max_import_file_size", defaultImportFileMaxSize) return nil } func loadTipConfiguration(config *Config, configFile *ini.File, sectionName string) error { config.LoginPageTips = getMultiLanguageContentConfig(configFile, sectionName, "enable_tips_in_login_page", "login_page_tips_content") return nil } func loadNotificationConfiguration(config *Config, configFile *ini.File, sectionName string) error { config.AfterRegisterNotification = getMultiLanguageContentConfig(configFile, sectionName, "enable_notification_after_register", "after_register_notification_content") config.AfterLoginNotification = getMultiLanguageContentConfig(configFile, sectionName, "enable_notification_after_login", "after_login_notification_content") config.AfterOpenNotification = getMultiLanguageContentConfig(configFile, sectionName, "enable_notification_after_open", "after_open_notification_content") return nil } func loadMapConfiguration(config *Config, configFile *ini.File, sectionName string) error { mapProvider := getConfigItemStringValue(configFile, sectionName, "map_provider") if mapProvider == "" { config.MapProvider = "" } else if mapProvider == OpenStreetMapProvider { config.MapProvider = OpenStreetMapProvider } else if mapProvider == OpenStreetMapHumanitarianStyleProvider { config.MapProvider = OpenStreetMapHumanitarianStyleProvider } else if mapProvider == OpenTopoMapProvider { config.MapProvider = OpenTopoMapProvider } else if mapProvider == OPNVKarteMapProvider { config.MapProvider = OPNVKarteMapProvider } else if mapProvider == CyclOSMMapProvider { config.MapProvider = CyclOSMMapProvider } else if mapProvider == CartoDBMapProvider { config.MapProvider = CartoDBMapProvider } else if mapProvider == TomTomMapProvider { config.MapProvider = TomTomMapProvider } else if mapProvider == TianDiTuProvider { config.MapProvider = TianDiTuProvider } else if mapProvider == GoogleMapProvider { config.MapProvider = GoogleMapProvider } else if mapProvider == BaiduMapProvider { config.MapProvider = BaiduMapProvider } else if mapProvider == AmapProvider { config.MapProvider = AmapProvider } else if mapProvider == CustomProvider { config.MapProvider = CustomProvider } else { return errs.ErrInvalidMapProvider } config.EnableMapDataFetchProxy = getConfigItemBoolValue(configFile, sectionName, "map_data_fetch_proxy", false) config.MapProxy = getConfigItemStringValue(configFile, sectionName, "proxy", "system") config.TomTomMapAPIKey = getConfigItemStringValue(configFile, sectionName, "tomtom_map_api_key") config.TianDiTuAPIKey = getConfigItemStringValue(configFile, sectionName, "tianditu_map_app_key") config.GoogleMapAPIKey = getConfigItemStringValue(configFile, sectionName, "google_map_api_key") config.BaiduMapAK = getConfigItemStringValue(configFile, sectionName, "baidu_map_ak") config.AmapApplicationKey = getConfigItemStringValue(configFile, sectionName, "amap_application_key") amapSecurityVerificationMethod := getConfigItemStringValue(configFile, sectionName, "amap_security_verification_method") if amapSecurityVerificationMethod == AmapSecurityVerificationInternalProxyMethod { config.AmapSecurityVerificationMethod = AmapSecurityVerificationInternalProxyMethod } else if amapSecurityVerificationMethod == AmapSecurityVerificationExternalProxyMethod { config.AmapSecurityVerificationMethod = AmapSecurityVerificationExternalProxyMethod } else if amapSecurityVerificationMethod == AmapSecurityVerificationPlainTextMethod { config.AmapSecurityVerificationMethod = AmapSecurityVerificationPlainTextMethod } else { return errs.ErrInvalidAmapSecurityVerificationMethod } config.AmapApplicationSecret = getConfigItemStringValue(configFile, sectionName, "amap_application_secret") config.AmapApiExternalProxyUrl = getConfigItemStringValue(configFile, sectionName, "amap_api_external_proxy_url") config.CustomMapTileServerTileLayerUrl = getConfigItemStringValue(configFile, sectionName, "custom_map_tile_server_url") config.CustomMapTileServerAnnotationLayerUrl = getConfigItemStringValue(configFile, sectionName, "custom_map_tile_server_annotation_url") config.CustomMapTileServerMinZoomLevel = getConfigItemUint8Value(configFile, sectionName, "custom_map_tile_server_min_zoom_level", 1) config.CustomMapTileServerMaxZoomLevel = getConfigItemUint8Value(configFile, sectionName, "custom_map_tile_server_max_zoom_level", 18) config.CustomMapTileServerDefaultZoomLevel = getConfigItemUint8Value(configFile, sectionName, "custom_map_tile_server_default_zoom_level", 14) return nil } func loadExchangeRatesConfiguration(config *Config, configFile *ini.File, sectionName string) error { dataSource := getConfigItemStringValue(configFile, sectionName, "data_source") if dataSource == BankOfCanadaDataSource || dataSource == CzechNationalBankDataSource || dataSource == DanmarksNationalbankDataSource || dataSource == EuroCentralBankDataSource || dataSource == NationalBankOfGeorgiaDataSource || dataSource == CentralBankOfHungaryDataSource || dataSource == BankOfIsraelDataSource || dataSource == CentralBankOfMyanmarDataSource || dataSource == NorgesBankDataSource || dataSource == NationalBankOfPolandDataSource || dataSource == NationalBankOfRomaniaDataSource || dataSource == BankOfRussiaDataSource || dataSource == SwissNationalBankDataSource || dataSource == NationalBankOfUkraineDataSource || dataSource == CentralBankOfUzbekistanDataSource || dataSource == UserCustomExchangeRatesDataSource { config.ExchangeRatesDataSource = dataSource } else { return errs.ErrInvalidExchangeRatesDataSource } config.ExchangeRatesProxy = getConfigItemStringValue(configFile, sectionName, "proxy", "system") config.ExchangeRatesRequestTimeout = getConfigItemUint32Value(configFile, sectionName, "request_timeout", defaultExchangeRatesDataRequestTimeout) if config.ExchangeRatesRequestTimeout > defaultExchangeRatesDataRequestTimeout { config.ExchangeRatesRequestTimeoutExceedDefaultValue = true } config.ExchangeRatesSkipTLSVerify = getConfigItemBoolValue(configFile, sectionName, "skip_tls_verify", false) return nil } func getWorkingPath() (string, error) { workingPath := os.Getenv(ebkWorkDirEnvName) if workingPath != "" { return workingPath, nil } execFilePath, err := os.Getwd() if err != nil { return "", err } return execFilePath, nil } func getFinalPath(workingPath, p string) (string, error) { var err error if !filepath.IsAbs(p) { p = filepath.Join(workingPath, p) } if _, err = os.Stat(p); err == nil { return p, nil } return p, err } func getMultiLanguageContentConfig(configFile *ini.File, sectionName string, enableKey string, contentKey string) MultiLanguageContentConfig { config := MultiLanguageContentConfig{ Enabled: getConfigItemBoolValue(configFile, sectionName, enableKey, false), DefaultContent: getConfigItemStringValue(configFile, sectionName, contentKey, ""), MultiLanguageContent: make(map[string]string), } for languageTag := range locales.AllLanguages { multiLanguageContentKey := strings.ToLower(languageTag) multiLanguageContentKey = strings.Replace(multiLanguageContentKey, "-", "_", -1) multiLanguageContentKey = contentKey + "_" + multiLanguageContentKey content := getConfigItemStringValue(configFile, sectionName, multiLanguageContentKey, "") if content != "" { config.MultiLanguageContent[languageTag] = content } } return config } func getConfigItemIsSet(configFile *ini.File, sectionName string, itemName string) bool { environmentValue := getConfigItemValueFromEnvironment(sectionName, itemName) if len(environmentValue) > 0 { return true } section := configFile.Section(sectionName) if !section.HasKey(itemName) { return false } return section.Key(itemName).Value() != "" } func getConfigItemStringValue(configFile *ini.File, sectionName string, itemName string, defaultValue ...string) string { environmentValue := getConfigItemValueFromEnvironment(sectionName, itemName) if len(environmentValue) > 0 { return environmentValue } section := configFile.Section(sectionName) key := section.Key(itemName) value := key.Value() if len(value) == 0 && len(defaultValue) > 0 { return defaultValue[0] } else { return value } } func getConfigItemUint8Value(configFile *ini.File, sectionName string, itemName string, defaultValue uint8) uint8 { environmentValue := getConfigItemValueFromEnvironment(sectionName, itemName) if len(environmentValue) > 0 { value, err := strconv.ParseUint(environmentValue, 10, 8) if err == nil { return uint8(value) } } section := configFile.Section(sectionName) value, err := strconv.ParseUint(section.Key(itemName).Value(), 10, 8) if err == nil { return uint8(value) } return defaultValue } func getConfigItemUint16Value(configFile *ini.File, sectionName string, itemName string, defaultValue uint16) uint16 { environmentValue := getConfigItemValueFromEnvironment(sectionName, itemName) if len(environmentValue) > 0 { value, err := strconv.ParseUint(environmentValue, 10, 16) if err == nil { return uint16(value) } } section := configFile.Section(sectionName) value, err := strconv.ParseUint(section.Key(itemName).Value(), 10, 16) if err == nil { return uint16(value) } return defaultValue } func getConfigItemUint32Value(configFile *ini.File, sectionName string, itemName string, defaultValue uint32) uint32 { environmentValue := getConfigItemValueFromEnvironment(sectionName, itemName) if len(environmentValue) > 0 { value, err := strconv.ParseUint(environmentValue, 10, 32) if err == nil { return uint32(value) } } section := configFile.Section(sectionName) value, err := strconv.ParseUint(section.Key(itemName).Value(), 10, 32) if err == nil { return uint32(value) } return defaultValue } func getConfigItemBoolValue(configFile *ini.File, sectionName string, itemName string, defaultValue bool) bool { environmentValue := getConfigItemValueFromEnvironment(sectionName, itemName) if len(environmentValue) > 0 { value, err := strconv.ParseBool(environmentValue) if err == nil { return value } } section := configFile.Section(sectionName) value, err := strconv.ParseBool(section.Key(itemName).Value()) if err == nil { return value } return defaultValue } func getConfigItemValueFromEnvironment(sectionName string, itemName string) string { itemFilePathEnvironmentKey := getConfigItemFilePathEnvironmentKey(sectionName, itemName) itemFilePath := os.Getenv(itemFilePathEnvironmentKey) if itemFilePath != "" { content, err := os.ReadFile(itemFilePath) if err == nil { return string(content) } } itemValueEnvironmentKey := getConfigItemValueEnvironmentKey(sectionName, itemName) itemValueEnvironmentValue := os.Getenv(itemValueEnvironmentKey) return itemValueEnvironmentValue } func getConfigItemFilePathEnvironmentKey(sectionName string, itemName string) string { return fmt.Sprintf("%s_%s_%s", ebkConfigItemFilePathEnvNamePrefix, strings.ToUpper(sectionName), strings.ToUpper(itemName)) } func getConfigItemValueEnvironmentKey(sectionName string, itemName string) string { return fmt.Sprintf("%s_%s_%s", ebkConfigItemValueEnvNamePrefix, strings.ToUpper(sectionName), strings.ToUpper(itemName)) } func getLogLevel(logLevelStr string) (Level, error) { if logLevelStr == "debug" { return LOGLEVEL_DEBUG, nil } else if logLevelStr == "info" { return LOGLEVEL_INFO, nil } else if logLevelStr == "warn" { return LOGLEVEL_WARN, nil } else if logLevelStr == "error" { return LOGLEVEL_ERROR, nil } return "", errs.ErrInvalidLogLevel }