package config import ( "bufio" "encoding/json" "errors" "flag" "fmt" "io" "io/ioutil" "os" "path/filepath" "runtime" "strconv" "strings" "time" "github.com/thrasher-corp/gocryptotrader/common" "github.com/thrasher-corp/gocryptotrader/common/convert" "github.com/thrasher-corp/gocryptotrader/common/file" "github.com/thrasher-corp/gocryptotrader/connchecker" "github.com/thrasher-corp/gocryptotrader/currency" "github.com/thrasher-corp/gocryptotrader/currency/forexprovider" "github.com/thrasher-corp/gocryptotrader/currency/forexprovider/base" "github.com/thrasher-corp/gocryptotrader/database" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" log "github.com/thrasher-corp/gocryptotrader/logger" ) // GetCurrencyConfig returns currency configurations func (c *Config) GetCurrencyConfig() CurrencyConfig { return c.Currency } // GetExchangeBankAccounts returns banking details associated with an exchange // for depositing funds func (c *Config) GetExchangeBankAccounts(exchangeName, depositingCurrency string) (BankAccount, error) { m.Lock() defer m.Unlock() for x := range c.Exchanges { if strings.EqualFold(c.Exchanges[x].Name, exchangeName) { for y := range c.Exchanges[x].BankAccounts { if strings.Contains(c.Exchanges[x].BankAccounts[y].SupportedCurrencies, depositingCurrency) { return c.Exchanges[x].BankAccounts[y], nil } } } } return BankAccount{}, fmt.Errorf("exchange %s bank details not found for %s", exchangeName, depositingCurrency) } // UpdateExchangeBankAccounts updates the configuration for the associated // exchange bank func (c *Config) UpdateExchangeBankAccounts(exchangeName string, bankCfg []BankAccount) error { m.Lock() defer m.Unlock() for i := range c.Exchanges { if strings.EqualFold(c.Exchanges[i].Name, exchangeName) { c.Exchanges[i].BankAccounts = bankCfg return nil } } return fmt.Errorf("exchange %s not found", exchangeName) } // GetClientBankAccounts returns banking details used for a given exchange // and currency func (c *Config) GetClientBankAccounts(exchangeName, targetCurrency string) (BankAccount, error) { m.Lock() defer m.Unlock() for x := range c.BankAccounts { if (strings.Contains(c.BankAccounts[x].SupportedExchanges, exchangeName) || c.BankAccounts[x].SupportedExchanges == "ALL") && strings.Contains(c.BankAccounts[x].SupportedCurrencies, targetCurrency) { return c.BankAccounts[x], nil } } return BankAccount{}, fmt.Errorf("client banking details not found for %s and currency %s", exchangeName, targetCurrency) } // UpdateClientBankAccounts updates the configuration for a bank func (c *Config) UpdateClientBankAccounts(bankCfg *BankAccount) error { m.Lock() defer m.Unlock() for i := range c.BankAccounts { if c.BankAccounts[i].BankName == bankCfg.BankName && c.BankAccounts[i].AccountNumber == bankCfg.AccountNumber { c.BankAccounts[i] = *bankCfg return nil } } return fmt.Errorf("client banking details for %s not found, update not applied", bankCfg.BankName) } // CheckClientBankAccounts checks client bank details func (c *Config) CheckClientBankAccounts() { m.Lock() defer m.Unlock() if len(c.BankAccounts) == 0 { c.BankAccounts = append(c.BankAccounts, BankAccount{ ID: "test-bank-01", BankName: "Test Bank", BankAddress: "42 Bank Street", BankPostalCode: "13337", BankPostalCity: "Satoshiville", BankCountry: "Japan", AccountName: "Satoshi Nakamoto", AccountNumber: "0234", SWIFTCode: "91272837", IBAN: "98218738671897", SupportedCurrencies: "USD", SupportedExchanges: "Kraken,Bitstamp", }, ) return } for i := range c.BankAccounts { if c.BankAccounts[i].Enabled { err := c.BankAccounts[i].Validate() if err != nil { c.BankAccounts[i].Enabled = false log.Warn(log.ConfigMgr, err.Error()) } } } } // GetBankAccountByID Returns a bank account based on its ID func (c *Config) GetBankAccountByID(id string) (*BankAccount, error) { m.Lock() defer m.Unlock() for x := range c.BankAccounts { if strings.EqualFold(c.BankAccounts[x].ID, id) { return &c.BankAccounts[x], nil } } return nil, fmt.Errorf(ErrBankAccountNotFound, id) } // PurgeExchangeAPICredentials purges the stored API credentials func (c *Config) PurgeExchangeAPICredentials() { m.Lock() defer m.Unlock() for x := range c.Exchanges { if !c.Exchanges[x].API.AuthenticatedSupport && !c.Exchanges[x].API.AuthenticatedWebsocketSupport { continue } c.Exchanges[x].API.AuthenticatedSupport = false c.Exchanges[x].API.AuthenticatedWebsocketSupport = false if c.Exchanges[x].API.CredentialsValidator.RequiresKey { c.Exchanges[x].API.Credentials.Key = DefaultAPIKey } if c.Exchanges[x].API.CredentialsValidator.RequiresSecret { c.Exchanges[x].API.Credentials.Secret = DefaultAPISecret } if c.Exchanges[x].API.CredentialsValidator.RequiresClientID { c.Exchanges[x].API.Credentials.ClientID = DefaultAPIClientID } c.Exchanges[x].API.Credentials.PEMKey = "" c.Exchanges[x].API.Credentials.OTPSecret = "" } } // GetCommunicationsConfig returns the communications configuration func (c *Config) GetCommunicationsConfig() CommunicationsConfig { m.Lock() defer m.Unlock() return c.Communications } // UpdateCommunicationsConfig sets a new updated version of a Communications // configuration func (c *Config) UpdateCommunicationsConfig(config *CommunicationsConfig) { m.Lock() c.Communications = *config m.Unlock() } // GetCryptocurrencyProviderConfig returns the communications configuration func (c *Config) GetCryptocurrencyProviderConfig() CryptocurrencyProvider { m.Lock() defer m.Unlock() return c.Currency.CryptocurrencyProvider } // UpdateCryptocurrencyProviderConfig returns the communications configuration func (c *Config) UpdateCryptocurrencyProviderConfig(config CryptocurrencyProvider) { m.Lock() c.Currency.CryptocurrencyProvider = config m.Unlock() } // CheckCommunicationsConfig checks to see if the variables are set correctly // from config.json func (c *Config) CheckCommunicationsConfig() { m.Lock() defer m.Unlock() // If the communications config hasn't been populated, populate // with example settings if c.Communications.SlackConfig.Name == "" { c.Communications.SlackConfig = SlackConfig{ Name: "Slack", TargetChannel: "general", VerificationToken: "testtest", } } if c.Communications.SMSGlobalConfig.Name == "" { if c.SMS != nil { if c.SMS.Contacts != nil { c.Communications.SMSGlobalConfig = SMSGlobalConfig{ Name: "SMSGlobal", Enabled: c.SMS.Enabled, Verbose: c.SMS.Verbose, Username: c.SMS.Username, Password: c.SMS.Password, Contacts: c.SMS.Contacts, } // flush old SMS config c.SMS = nil } else { c.Communications.SMSGlobalConfig = SMSGlobalConfig{ Name: "SMSGlobal", From: c.Name, Username: "main", Password: "test", Contacts: []SMSContact{ { Name: "bob", Number: "1234", Enabled: false, }, }, } } } else { c.Communications.SMSGlobalConfig = SMSGlobalConfig{ Name: "SMSGlobal", Username: "main", Password: "test", Contacts: []SMSContact{ { Name: "bob", Number: "1234", Enabled: false, }, }, } } } else { if c.Communications.SMSGlobalConfig.From == "" { c.Communications.SMSGlobalConfig.From = c.Name } if len(c.Communications.SMSGlobalConfig.From) > 11 { log.Warnf(log.ConfigMgr, "SMSGlobal config supplied from name exceeds 11 characters, trimming.\n") c.Communications.SMSGlobalConfig.From = c.Communications.SMSGlobalConfig.From[:11] } if c.SMS != nil { // flush old SMS config c.SMS = nil } } if c.Communications.SMTPConfig.Name == "" { c.Communications.SMTPConfig = SMTPConfig{ Name: "SMTP", Host: "smtp.google.com", Port: "537", AccountName: "some", AccountPassword: "password", RecipientList: "lol123@gmail.com", } } if c.Communications.TelegramConfig.Name == "" { c.Communications.TelegramConfig = TelegramConfig{ Name: "Telegram", VerificationToken: "testest", } } if c.Communications.SlackConfig.Name != "Slack" || c.Communications.SMSGlobalConfig.Name != "SMSGlobal" || c.Communications.SMTPConfig.Name != "SMTP" || c.Communications.TelegramConfig.Name != "Telegram" { log.Warnln(log.ConfigMgr, "Communications config name/s not set correctly") } if c.Communications.SlackConfig.Enabled { if c.Communications.SlackConfig.TargetChannel == "" || c.Communications.SlackConfig.VerificationToken == "" || c.Communications.SlackConfig.VerificationToken == "testtest" { c.Communications.SlackConfig.Enabled = false log.Warnln(log.ConfigMgr, "Slack enabled in config but variable data not set, disabling.") } } if c.Communications.SMSGlobalConfig.Enabled { if c.Communications.SMSGlobalConfig.Username == "" || c.Communications.SMSGlobalConfig.Password == "" || len(c.Communications.SMSGlobalConfig.Contacts) == 0 { c.Communications.SMSGlobalConfig.Enabled = false log.Warnln(log.ConfigMgr, "SMSGlobal enabled in config but variable data not set, disabling.") } } if c.Communications.SMTPConfig.Enabled { if c.Communications.SMTPConfig.Host == "" || c.Communications.SMTPConfig.Port == "" || c.Communications.SMTPConfig.AccountName == "" || c.Communications.SMTPConfig.AccountPassword == "" { c.Communications.SMTPConfig.Enabled = false log.Warnln(log.ConfigMgr, "SMTP enabled in config but variable data not set, disabling.") } } if c.Communications.TelegramConfig.Enabled { if c.Communications.TelegramConfig.VerificationToken == "" { c.Communications.TelegramConfig.Enabled = false log.Warnln(log.ConfigMgr, "Telegram enabled in config but variable data not set, disabling.") } } } // GetExchangeAssetTypes returns the exchanges supported asset types func (c *Config) GetExchangeAssetTypes(exchName string) (asset.Items, error) { exchCfg, err := c.GetExchangeConfig(exchName) if err != nil { return nil, err } if exchCfg.CurrencyPairs == nil { return nil, fmt.Errorf("exchange %s currency pairs is nil", exchName) } return exchCfg.CurrencyPairs.AssetTypes, nil } // SupportsExchangeAssetType returns whether or not the exchange supports the supplied asset type func (c *Config) SupportsExchangeAssetType(exchName string, assetType asset.Item) (bool, error) { exchCfg, err := c.GetExchangeConfig(exchName) if err != nil { return false, err } if exchCfg.CurrencyPairs == nil { return false, fmt.Errorf("exchange %s currency pairs is nil", exchName) } if !asset.IsValid(assetType) { return false, fmt.Errorf("exchange %s invalid asset types", exchName) } return exchCfg.CurrencyPairs.AssetTypes.Contains(assetType), nil } // CheckExchangeAssetsConsistency checks the exchanges supported assets compared to the stored // entries and removes any non supported func (c *Config) CheckExchangeAssetsConsistency(exchName string) { exchCfg, err := c.GetExchangeConfig(exchName) if err != nil { return } exchangeAssetTypes, err := c.GetExchangeAssetTypes(exchName) if err != nil { return } storedAssetTypes := exchCfg.CurrencyPairs.GetAssetTypes() for x := range storedAssetTypes { if !exchangeAssetTypes.Contains(storedAssetTypes[x]) { log.Warnf(log.ConfigMgr, "%s has non-needed stored asset type %v. Removing..\n", exchName, storedAssetTypes[x]) exchCfg.CurrencyPairs.Delete(storedAssetTypes[x]) } } } // SetPairs sets the exchanges currency pairs func (c *Config) SetPairs(exchName string, assetType asset.Item, enabled bool, pairs currency.Pairs) error { if len(pairs) == 0 { return fmt.Errorf("pairs is nil") } exchCfg, err := c.GetExchangeConfig(exchName) if err != nil { return err } supports, err := c.SupportsExchangeAssetType(exchName, assetType) if err != nil { return err } if !supports { return fmt.Errorf("exchange %s does not support asset type %v", exchName, assetType) } exchCfg.CurrencyPairs.StorePairs(assetType, pairs, enabled) return nil } // GetCurrencyPairConfig returns currency pair config for the desired exchange and asset type func (c *Config) GetCurrencyPairConfig(exchName string, assetType asset.Item) (*currency.PairStore, error) { exchCfg, err := c.GetExchangeConfig(exchName) if err != nil { return nil, err } supports, err := c.SupportsExchangeAssetType(exchName, assetType) if err != nil { return nil, err } if !supports { return nil, fmt.Errorf("exchange %s does not support asset type %v", exchName, assetType) } return exchCfg.CurrencyPairs.Get(assetType), nil } // CheckPairConfigFormats checks to see if the pair config format is valid func (c *Config) CheckPairConfigFormats(exchName string) error { assetTypes, err := c.GetExchangeAssetTypes(exchName) if err != nil { return err } for x := range assetTypes { assetType := assetTypes[x] pairFmt, err := c.GetPairFormat(exchName, assetType) if err != nil { return err } // No err checking is required as the above checks the same // conditions pairs, _ := c.GetCurrencyPairConfig(exchName, assetType) if len(pairs.Available) == 0 || len(pairs.Enabled) == 0 { continue } checker := func(enabled bool) error { pairsType := "enabled" loadedPairs := pairs.Enabled if !enabled { pairsType = "available" loadedPairs = pairs.Available } for y := range loadedPairs { if pairFmt.Delimiter != "" && pairFmt.Index != "" { return fmt.Errorf( "exchange %s %s %s cannot have an index and delimiter set at the same time", exchName, pairsType, assetType) } if pairFmt.Delimiter != "" { if !strings.Contains(loadedPairs[y].String(), pairFmt.Delimiter) { return fmt.Errorf( "exchange %s %s %s pairs does not contain delimiter", exchName, pairsType, assetType) } } if pairFmt.Index != "" { if !strings.Contains(loadedPairs[y].String(), pairFmt.Index) { return fmt.Errorf("exchange %s %s %s pairs does not contain an index", exchName, pairsType, assetType) } } } return nil } err = checker(true) if err != nil { return err } err = checker(false) if err != nil { return err } } return nil } // CheckPairConsistency checks to see if the enabled pair exists in the // available pairs list func (c *Config) CheckPairConsistency(exchName string) error { assetTypes, err := c.GetExchangeAssetTypes(exchName) if err != nil { return err } for x := range assetTypes { enabledPairs, err := c.GetEnabledPairs(exchName, assetTypes[x]) if err != nil { return err } availPairs, _ := c.GetAvailablePairs(exchName, assetTypes[x]) if len(availPairs) == 0 { continue } var pairs, pairsRemoved currency.Pairs update := false if len(enabledPairs) > 0 { for x := range enabledPairs { if !availPairs.Contains(enabledPairs[x], true) { update = true pairsRemoved = append(pairsRemoved, enabledPairs[x]) continue } pairs = append(pairs, enabledPairs[x]) } } else { update = true } if !update { continue } if len(pairs) == 0 || len(enabledPairs) == 0 { newPair := availPairs.GetRandomPair() c.SetPairs(exchName, assetTypes[x], true, currency.Pairs{newPair}) log.Warnf(log.ExchangeSys, "Exchange %s: [%v] No enabled pairs found in available pairs, randomly added %v pair.\n", exchName, assetTypes[x], newPair) continue } else { c.SetPairs(exchName, assetTypes[x], true, pairs) } log.Warnf(log.ExchangeSys, "Exchange %s: [%v] Removing enabled pair(s) %v from enabled pairs as it isn't an available pair.\n", exchName, assetTypes[x], pairsRemoved.Strings()) } return nil } // SupportsPair returns true or not whether the exchange supports the supplied // pair func (c *Config) SupportsPair(exchName string, p currency.Pair, assetType asset.Item) (bool, error) { pairs, err := c.GetAvailablePairs(exchName, assetType) if err != nil { return false, err } return pairs.Contains(p, false), nil } // GetPairFormat returns the exchanges pair config storage format func (c *Config) GetPairFormat(exchName string, assetType asset.Item) (currency.PairFormat, error) { exchCfg, err := c.GetExchangeConfig(exchName) if err != nil { return currency.PairFormat{}, err } supports, err := c.SupportsExchangeAssetType(exchName, assetType) if err != nil { return currency.PairFormat{}, err } if !supports { return currency.PairFormat{}, fmt.Errorf("exchange %s does not support asset type %s", exchName, assetType) } if exchCfg.CurrencyPairs.UseGlobalFormat { return *exchCfg.CurrencyPairs.ConfigFormat, nil } p := exchCfg.CurrencyPairs.Get(assetType) if p == nil { return currency.PairFormat{}, fmt.Errorf("exchange %s pair store for asset type %s is nil", exchName, assetType) } return *p.ConfigFormat, nil } // GetAvailablePairs returns a list of currency pairs for a specifc exchange func (c *Config) GetAvailablePairs(exchName string, assetType asset.Item) (currency.Pairs, error) { exchCfg, err := c.GetExchangeConfig(exchName) if err != nil { return nil, err } pairFormat, err := c.GetPairFormat(exchName, assetType) if err != nil { return nil, err } pairs := exchCfg.CurrencyPairs.GetPairs(assetType, false) if pairs == nil { return nil, nil } return pairs.Format(pairFormat.Delimiter, pairFormat.Index, pairFormat.Uppercase), nil } // GetEnabledPairs returns a list of currency pairs for a specifc exchange func (c *Config) GetEnabledPairs(exchName string, assetType asset.Item) ([]currency.Pair, error) { exchCfg, err := c.GetExchangeConfig(exchName) if err != nil { return nil, err } pairFormat, err := c.GetPairFormat(exchName, assetType) if err != nil { return nil, err } pairs := exchCfg.CurrencyPairs.GetPairs(assetType, true) if pairs == nil { return nil, nil } return pairs.Format(pairFormat.Delimiter, pairFormat.Index, pairFormat.Uppercase), nil } // GetEnabledExchanges returns a list of enabled exchanges func (c *Config) GetEnabledExchanges() []string { var enabledExchs []string for i := range c.Exchanges { if c.Exchanges[i].Enabled { enabledExchs = append(enabledExchs, c.Exchanges[i].Name) } } return enabledExchs } // GetDisabledExchanges returns a list of disabled exchanges func (c *Config) GetDisabledExchanges() []string { var disabledExchs []string for i := range c.Exchanges { if !c.Exchanges[i].Enabled { disabledExchs = append(disabledExchs, c.Exchanges[i].Name) } } return disabledExchs } // CountEnabledExchanges returns the number of exchanges that are enabled. func (c *Config) CountEnabledExchanges() int { counter := 0 for i := range c.Exchanges { if c.Exchanges[i].Enabled { counter++ } } return counter } // GetCurrencyPairDisplayConfig retrieves the currency pair display preference func (c *Config) GetCurrencyPairDisplayConfig() *CurrencyPairFormatConfig { return c.Currency.CurrencyPairFormat } // GetAllExchangeConfigs returns all exchange configurations func (c *Config) GetAllExchangeConfigs() []ExchangeConfig { m.Lock() defer m.Unlock() return c.Exchanges } // GetExchangeConfig returns exchange configurations by its indivdual name func (c *Config) GetExchangeConfig(name string) (*ExchangeConfig, error) { m.Lock() defer m.Unlock() for i := range c.Exchanges { if strings.EqualFold(c.Exchanges[i].Name, name) { return &c.Exchanges[i], nil } } return nil, fmt.Errorf(ErrExchangeNotFound, name) } // GetForexProviderConfig returns a forex provider configuration by its name func (c *Config) GetForexProviderConfig(name string) (base.Settings, error) { m.Lock() defer m.Unlock() for i := range c.Currency.ForexProviders { if strings.EqualFold(c.Currency.ForexProviders[i].Name, name) { return c.Currency.ForexProviders[i], nil } } return base.Settings{}, errors.New("provider not found") } // GetForexProvidersConfig returns a list of available forex providers func (c *Config) GetForexProvidersConfig() []base.Settings { m.Lock() defer m.Unlock() return c.Currency.ForexProviders } // GetPrimaryForexProvider returns the primary forex provider func (c *Config) GetPrimaryForexProvider() string { m.Lock() defer m.Unlock() for i := range c.Currency.ForexProviders { if c.Currency.ForexProviders[i].PrimaryProvider { return c.Currency.ForexProviders[i].Name } } return "" } // UpdateExchangeConfig updates exchange configurations func (c *Config) UpdateExchangeConfig(e *ExchangeConfig) error { m.Lock() defer m.Unlock() for i := range c.Exchanges { if strings.EqualFold(c.Exchanges[i].Name, e.Name) { c.Exchanges[i] = *e return nil } } return fmt.Errorf(ErrExchangeNotFound, e.Name) } // CheckExchangeConfigValues returns configuation values for all enabled // exchanges func (c *Config) CheckExchangeConfigValues() error { if len(c.Exchanges) == 0 { return errors.New("no exchange configs found") } exchanges := 0 for i := range c.Exchanges { if strings.EqualFold(c.Exchanges[i].Name, "GDAX") { c.Exchanges[i].Name = "CoinbasePro" } // Check to see if the old API storage format is used if c.Exchanges[i].APIKey != nil { // It is, migrate settings to new format c.Exchanges[i].API.AuthenticatedSupport = *c.Exchanges[i].AuthenticatedAPISupport if c.Exchanges[i].AuthenticatedWebsocketAPISupport != nil { c.Exchanges[i].API.AuthenticatedWebsocketSupport = *c.Exchanges[i].AuthenticatedWebsocketAPISupport } c.Exchanges[i].API.Credentials.Key = *c.Exchanges[i].APIKey c.Exchanges[i].API.Credentials.Secret = *c.Exchanges[i].APISecret if c.Exchanges[i].APIAuthPEMKey != nil { c.Exchanges[i].API.Credentials.PEMKey = *c.Exchanges[i].APIAuthPEMKey } if c.Exchanges[i].APIAuthPEMKeySupport != nil { c.Exchanges[i].API.PEMKeySupport = *c.Exchanges[i].APIAuthPEMKeySupport } if c.Exchanges[i].ClientID != nil { c.Exchanges[i].API.Credentials.ClientID = *c.Exchanges[i].ClientID } if c.Exchanges[i].WebsocketURL != nil { c.Exchanges[i].API.Endpoints.WebsocketURL = *c.Exchanges[i].WebsocketURL } c.Exchanges[i].API.Endpoints.URL = *c.Exchanges[i].APIURL c.Exchanges[i].API.Endpoints.URLSecondary = *c.Exchanges[i].APIURLSecondary // Flush settings c.Exchanges[i].AuthenticatedAPISupport = nil c.Exchanges[i].AuthenticatedWebsocketAPISupport = nil c.Exchanges[i].APIKey = nil c.Exchanges[i].APISecret = nil c.Exchanges[i].ClientID = nil c.Exchanges[i].APIAuthPEMKeySupport = nil c.Exchanges[i].APIAuthPEMKey = nil c.Exchanges[i].APIURL = nil c.Exchanges[i].APIURLSecondary = nil c.Exchanges[i].WebsocketURL = nil } if c.Exchanges[i].Features == nil { c.Exchanges[i].Features = &FeaturesConfig{} } if c.Exchanges[i].SupportsAutoPairUpdates != nil { c.Exchanges[i].Features.Supports.RESTCapabilities.AutoPairUpdates = *c.Exchanges[i].SupportsAutoPairUpdates c.Exchanges[i].Features.Enabled.AutoPairUpdates = *c.Exchanges[i].SupportsAutoPairUpdates c.Exchanges[i].SupportsAutoPairUpdates = nil } if c.Exchanges[i].Websocket != nil { c.Exchanges[i].Features.Enabled.Websocket = *c.Exchanges[i].Websocket c.Exchanges[i].Websocket = nil } if c.Exchanges[i].API.Endpoints.URL != APIURLNonDefaultMessage { if c.Exchanges[i].API.Endpoints.URL == "" { // Set default if nothing set c.Exchanges[i].API.Endpoints.URL = APIURLNonDefaultMessage } } if c.Exchanges[i].API.Endpoints.URLSecondary != APIURLNonDefaultMessage { if c.Exchanges[i].API.Endpoints.URLSecondary == "" { // Set default if nothing set c.Exchanges[i].API.Endpoints.URLSecondary = APIURLNonDefaultMessage } } if c.Exchanges[i].API.Endpoints.WebsocketURL != WebsocketURLNonDefaultMessage { if c.Exchanges[i].API.Endpoints.WebsocketURL == "" { c.Exchanges[i].API.Endpoints.WebsocketURL = WebsocketURLNonDefaultMessage } } // Check if see if the new currency pairs format is empty and flesh it out if so if c.Exchanges[i].CurrencyPairs == nil { c.Exchanges[i].CurrencyPairs = new(currency.PairsManager) c.Exchanges[i].CurrencyPairs.Pairs = make(map[asset.Item]*currency.PairStore) if c.Exchanges[i].PairsLastUpdated != nil { c.Exchanges[i].CurrencyPairs.LastUpdated = *c.Exchanges[i].PairsLastUpdated } c.Exchanges[i].CurrencyPairs.ConfigFormat = c.Exchanges[i].ConfigCurrencyPairFormat c.Exchanges[i].CurrencyPairs.RequestFormat = c.Exchanges[i].RequestCurrencyPairFormat if c.Exchanges[i].AssetTypes == nil { c.Exchanges[i].CurrencyPairs.AssetTypes = asset.Items{ asset.Spot, } } else { c.Exchanges[i].CurrencyPairs.AssetTypes = asset.New( strings.ToLower(*c.Exchanges[i].AssetTypes), ) } var availPairs, enabledPairs currency.Pairs if c.Exchanges[i].AvailablePairs != nil { availPairs = *c.Exchanges[i].AvailablePairs } if c.Exchanges[i].EnabledPairs != nil { enabledPairs = *c.Exchanges[i].EnabledPairs } c.Exchanges[i].CurrencyPairs.UseGlobalFormat = true c.Exchanges[i].CurrencyPairs.Store(asset.Spot, currency.PairStore{ Available: availPairs, Enabled: enabledPairs, }, ) // flush old values c.Exchanges[i].PairsLastUpdated = nil c.Exchanges[i].ConfigCurrencyPairFormat = nil c.Exchanges[i].RequestCurrencyPairFormat = nil c.Exchanges[i].AssetTypes = nil c.Exchanges[i].AvailablePairs = nil c.Exchanges[i].EnabledPairs = nil } if c.Exchanges[i].Enabled { if c.Exchanges[i].Name == "" { log.Errorf(log.ConfigMgr, ErrExchangeNameEmpty, i) c.Exchanges[i].Enabled = false continue } if (c.Exchanges[i].API.AuthenticatedSupport || c.Exchanges[i].API.AuthenticatedWebsocketSupport) && c.Exchanges[i].API.CredentialsValidator != nil { var failed bool if c.Exchanges[i].API.CredentialsValidator.RequiresKey && (c.Exchanges[i].API.Credentials.Key == "" || c.Exchanges[i].API.Credentials.Key == DefaultAPIKey) { failed = true } if c.Exchanges[i].API.CredentialsValidator.RequiresSecret && (c.Exchanges[i].API.Credentials.Secret == "" || c.Exchanges[i].API.Credentials.Secret == DefaultAPISecret) { failed = true } if c.Exchanges[i].API.CredentialsValidator.RequiresClientID && (c.Exchanges[i].API.Credentials.ClientID == DefaultAPIClientID || c.Exchanges[i].API.Credentials.ClientID == "") { failed = true } if failed { c.Exchanges[i].API.AuthenticatedSupport = false c.Exchanges[i].API.AuthenticatedWebsocketSupport = false log.Warnf(log.ExchangeSys, WarningExchangeAuthAPIDefaultOrEmptyValues, c.Exchanges[i].Name) } } if !c.Exchanges[i].Features.Supports.RESTCapabilities.AutoPairUpdates && !c.Exchanges[i].Features.Supports.WebsocketCapabilities.AutoPairUpdates { lastUpdated := convert.UnixTimestampToTime(c.Exchanges[i].CurrencyPairs.LastUpdated) lastUpdated = lastUpdated.AddDate(0, 0, pairsLastUpdatedWarningThreshold) if lastUpdated.Unix() <= time.Now().Unix() { log.Warnf(log.ExchangeSys, WarningPairsLastUpdatedThresholdExceeded, c.Exchanges[i].Name, pairsLastUpdatedWarningThreshold) } } if c.Exchanges[i].HTTPTimeout <= 0 { log.Warnf(log.ExchangeSys, "Exchange %s HTTP Timeout value not set, defaulting to %v.\n", c.Exchanges[i].Name, defaultHTTPTimeout) c.Exchanges[i].HTTPTimeout = defaultHTTPTimeout } if c.Exchanges[i].HTTPRateLimiter != nil { if c.Exchanges[i].HTTPRateLimiter.Authenticated.Duration < 0 { log.Warnf(log.ExchangeSys, "Exchange %s HTTP Rate Limiter authenticated duration set to negative value, defaulting to 0\n", c.Exchanges[i].Name) c.Exchanges[i].HTTPRateLimiter.Authenticated.Duration = 0 } if c.Exchanges[i].HTTPRateLimiter.Authenticated.Rate < 0 { log.Warnf(log.ExchangeSys, "Exchange %s HTTP Rate Limiter authenticated rate set to negative value, defaulting to 0\n", c.Exchanges[i].Name) c.Exchanges[i].HTTPRateLimiter.Authenticated.Rate = 0 } if c.Exchanges[i].HTTPRateLimiter.Unauthenticated.Duration < 0 { log.Warnf(log.ExchangeSys, "Exchange %s HTTP Rate Limiter unauthenticated duration set to negative value, defaulting to 0\n", c.Exchanges[i].Name) c.Exchanges[i].HTTPRateLimiter.Unauthenticated.Duration = 0 } if c.Exchanges[i].HTTPRateLimiter.Unauthenticated.Rate < 0 { log.Warnf(log.ExchangeSys, "Exchange %s HTTP Rate Limiter unauthenticated rate set to negative value, defaulting to 0\n", c.Exchanges[i].Name) c.Exchanges[i].HTTPRateLimiter.Unauthenticated.Rate = 0 } } if c.Exchanges[i].WebsocketResponseCheckTimeout <= 0 { log.Warnf(log.ExchangeSys, "Exchange %s Websocket response check timeout value not set, defaulting to %v.", c.Exchanges[i].Name, defaultWebsocketResponseCheckTimeout) c.Exchanges[i].WebsocketResponseCheckTimeout = defaultWebsocketResponseCheckTimeout } if c.Exchanges[i].WebsocketResponseMaxLimit <= 0 { log.Warnf(log.ExchangeSys, "Exchange %s Websocket response max limit value not set, defaulting to %v.", c.Exchanges[i].Name, defaultWebsocketResponseMaxLimit) c.Exchanges[i].WebsocketResponseMaxLimit = defaultWebsocketResponseMaxLimit } if c.Exchanges[i].WebsocketTrafficTimeout <= 0 { log.Warnf(log.ExchangeSys, "Exchange %s Websocket response traffic timeout value not set, defaulting to %v.", c.Exchanges[i].Name, defaultWebsocketTrafficTimeout) c.Exchanges[i].WebsocketTrafficTimeout = defaultWebsocketTrafficTimeout } if c.Exchanges[i].WebsocketOrderbookBufferLimit <= 0 { log.Warnf(log.ExchangeSys, "Exchange %s Websocket orderbook buffer limit value not set, defaulting to %v.", c.Exchanges[i].Name, defaultWebsocketOrderbookBufferLimit) c.Exchanges[i].WebsocketOrderbookBufferLimit = defaultWebsocketOrderbookBufferLimit } err := c.CheckPairConsistency(c.Exchanges[i].Name) if err != nil { log.Errorf(log.ExchangeSys, "Exchange %s: CheckPairConsistency error: %s\n", c.Exchanges[i].Name, err) c.Exchanges[i].Enabled = false continue } c.CheckExchangeAssetsConsistency(c.Exchanges[i].Name) for x := range c.Exchanges[i].BankAccounts { if !c.Exchanges[i].BankAccounts[x].Enabled { continue } err := c.Exchanges[i].BankAccounts[x].Validate() if err != nil { c.Exchanges[i].BankAccounts[x].Enabled = false log.Warn(log.ConfigMgr, err.Error()) } } exchanges++ } } if exchanges == 0 { return errors.New(ErrNoEnabledExchanges) } return nil } // CheckCurrencyConfigValues checks to see if the currency config values are correct or not func (c *Config) CheckCurrencyConfigValues() error { fxProviders := forexprovider.GetSupportedForexProviders() if len(fxProviders) != len(c.Currency.ForexProviders) { for x := range fxProviders { _, err := c.GetForexProviderConfig(fxProviders[x]) if err != nil { log.Warnf(log.Global, "%s forex provider not found, adding to config..\n", fxProviders[x]) c.Currency.ForexProviders = append(c.Currency.ForexProviders, base.Settings{ Name: fxProviders[x], RESTPollingDelay: 600, APIKey: DefaultUnsetAPIKey, APIKeyLvl: -1, }) } } } count := 0 for i := range c.Currency.ForexProviders { if c.Currency.ForexProviders[i].Enabled { if c.Currency.ForexProviders[i].Name == "CurrencyConverter" && c.Currency.ForexProviders[i].PrimaryProvider && (c.Currency.ForexProviders[i].APIKey == "" || c.Currency.ForexProviders[i].APIKey == DefaultUnsetAPIKey) { log.Warnln(log.Global, "CurrencyConverter forex provider no longer supports unset API key requests. Switching to ExchangeRates FX provider..") c.Currency.ForexProviders[i].Enabled = false c.Currency.ForexProviders[i].PrimaryProvider = false c.Currency.ForexProviders[i].APIKey = DefaultUnsetAPIKey c.Currency.ForexProviders[i].APIKeyLvl = -1 continue } if c.Currency.ForexProviders[i].APIKey == DefaultUnsetAPIKey && c.Currency.ForexProviders[i].Name != DefaultForexProviderExchangeRatesAPI { log.Warnf(log.Global, "%s enabled forex provider API key not set. Please set this in your config.json file\n", c.Currency.ForexProviders[i].Name) c.Currency.ForexProviders[i].Enabled = false c.Currency.ForexProviders[i].PrimaryProvider = false continue } if c.Currency.ForexProviders[i].APIKeyLvl == -1 && c.Currency.ForexProviders[i].Name != DefaultForexProviderExchangeRatesAPI { log.Warnf(log.Global, "%s APIKey Level not set, functions limited. Please set this in your config.json file\n", c.Currency.ForexProviders[i].Name) } count++ } } if count == 0 { for x := range c.Currency.ForexProviders { if c.Currency.ForexProviders[x].Name == DefaultForexProviderExchangeRatesAPI { c.Currency.ForexProviders[x].Enabled = true c.Currency.ForexProviders[x].PrimaryProvider = true log.Warnln(log.ConfigMgr, "Using ExchangeRatesAPI for default forex provider.") } } } if c.Currency.CryptocurrencyProvider == (CryptocurrencyProvider{}) { c.Currency.CryptocurrencyProvider.Name = "CoinMarketCap" c.Currency.CryptocurrencyProvider.Enabled = false c.Currency.CryptocurrencyProvider.Verbose = false c.Currency.CryptocurrencyProvider.AccountPlan = DefaultUnsetAccountPlan c.Currency.CryptocurrencyProvider.APIkey = DefaultUnsetAPIKey } if c.Currency.CryptocurrencyProvider.Enabled { if c.Currency.CryptocurrencyProvider.APIkey == "" || c.Currency.CryptocurrencyProvider.APIkey == DefaultUnsetAPIKey { log.Warnln(log.ConfigMgr, "CryptocurrencyProvider enabled but api key is unset please set this in your config.json file") } if c.Currency.CryptocurrencyProvider.AccountPlan == "" || c.Currency.CryptocurrencyProvider.AccountPlan == DefaultUnsetAccountPlan { log.Warnln(log.ConfigMgr, "CryptocurrencyProvider enabled but account plan is unset please set this in your config.json file") } } else { if c.Currency.CryptocurrencyProvider.APIkey == "" { c.Currency.CryptocurrencyProvider.APIkey = DefaultUnsetAPIKey } if c.Currency.CryptocurrencyProvider.AccountPlan == "" { c.Currency.CryptocurrencyProvider.AccountPlan = DefaultUnsetAccountPlan } } if c.Currency.Cryptocurrencies.Join() == "" { if c.Cryptocurrencies != nil { c.Currency.Cryptocurrencies = *c.Cryptocurrencies c.Cryptocurrencies = nil } else { c.Currency.Cryptocurrencies = currency.GetDefaultCryptocurrencies() } } if c.Currency.CurrencyPairFormat == nil { if c.CurrencyPairFormat != nil { c.Currency.CurrencyPairFormat = c.CurrencyPairFormat c.CurrencyPairFormat = nil } else { c.Currency.CurrencyPairFormat = &CurrencyPairFormatConfig{ Delimiter: "-", Uppercase: true, } } } if c.Currency.FiatDisplayCurrency.IsEmpty() { if c.FiatDisplayCurrency != nil { c.Currency.FiatDisplayCurrency = *c.FiatDisplayCurrency c.FiatDisplayCurrency = nil } else { c.Currency.FiatDisplayCurrency = currency.USD } } // Flush old setting which still exists if c.FiatDisplayCurrency != nil { c.FiatDisplayCurrency = nil } return nil } // RetrieveConfigCurrencyPairs splits, assigns and verifies enabled currency // pairs either cryptoCurrencies or fiatCurrencies func (c *Config) RetrieveConfigCurrencyPairs(enabledOnly bool, assetType asset.Item) error { cryptoCurrencies := c.Currency.Cryptocurrencies fiatCurrencies := currency.GetFiatCurrencies() for x := range c.Exchanges { if !c.Exchanges[x].Enabled && enabledOnly { continue } supports, _ := c.SupportsExchangeAssetType(c.Exchanges[x].Name, assetType) if !supports { continue } baseCurrencies := c.Exchanges[x].BaseCurrencies for y := range baseCurrencies { if !fiatCurrencies.Contains(baseCurrencies[y]) { fiatCurrencies = append(fiatCurrencies, baseCurrencies[y]) } } } for x := range c.Exchanges { supports, _ := c.SupportsExchangeAssetType(c.Exchanges[x].Name, assetType) if !supports { continue } var pairs []currency.Pair var err error if !c.Exchanges[x].Enabled && enabledOnly { pairs, err = c.GetEnabledPairs(c.Exchanges[x].Name, assetType) } else { pairs, err = c.GetAvailablePairs(c.Exchanges[x].Name, assetType) } if err != nil { return err } for y := range pairs { if !fiatCurrencies.Contains(pairs[y].Base) && !cryptoCurrencies.Contains(pairs[y].Base) { cryptoCurrencies = append(cryptoCurrencies, pairs[y].Base) } if !fiatCurrencies.Contains(pairs[y].Quote) && !cryptoCurrencies.Contains(pairs[y].Quote) { cryptoCurrencies = append(cryptoCurrencies, pairs[y].Quote) } } } currency.UpdateCurrencies(fiatCurrencies, false) currency.UpdateCurrencies(cryptoCurrencies, true) return nil } // CheckLoggerConfig checks to see logger values are present and valid in config // if not creates a default instance of the logger func (c *Config) CheckLoggerConfig() error { m.Lock() defer m.Unlock() if c.Logging.Enabled == nil || c.Logging.Output == "" { c.Logging = log.GenDefaultSettings() } f := func(f bool) *bool { return &f }(false) if c.Logging.AdvancedSettings.ShowLogSystemName == nil { c.Logging.AdvancedSettings.ShowLogSystemName = f } if c.Logging.LoggerFileConfig != nil { if c.Logging.LoggerFileConfig.FileName == "" { c.Logging.LoggerFileConfig.FileName = "log.txt" } if c.Logging.LoggerFileConfig.Rotate == nil { c.Logging.LoggerFileConfig.Rotate = f } if c.Logging.LoggerFileConfig.MaxSize < 0 { c.Logging.LoggerFileConfig.MaxSize = 100 } log.FileLoggingConfiguredCorrectly = true } log.GlobalLogConfig = &c.Logging logPath := filepath.Join(common.GetDefaultDataDir(runtime.GOOS), "logs") err := common.CreateDir(logPath) if err != nil { return err } log.LogPath = logPath return nil } func (c *Config) checkDatabaseConfig() error { m.Lock() defer m.Unlock() if (c.Database == database.Config{}) { c.Database.Driver = database.DBSQLite3 c.Database.Database = database.DefaultSQLiteDatabase } if !c.Database.Enabled { return nil } if !common.StringDataCompare(database.SupportedDrivers, c.Database.Driver) { c.Database.Enabled = false return fmt.Errorf("unsupported database driver %v, database disabled", c.Database.Driver) } if c.Database.Driver == database.DBSQLite || c.Database.Driver == database.DBSQLite3 { databaseDir := filepath.Join(common.GetDefaultDataDir(runtime.GOOS), "/database") err := common.CreateDir(databaseDir) if err != nil { return err } database.DB.DataPath = databaseDir } database.DB.Config = &c.Database return nil } // CheckNTPConfig checks for missing or incorrectly configured NTPClient and recreates with known safe defaults func (c *Config) CheckNTPConfig() { m.Lock() defer m.Unlock() if c.NTPClient.AllowedDifference == nil || *c.NTPClient.AllowedDifference == 0 { c.NTPClient.AllowedDifference = new(time.Duration) *c.NTPClient.AllowedDifference = defaultNTPAllowedDifference } if c.NTPClient.AllowedNegativeDifference == nil || *c.NTPClient.AllowedNegativeDifference <= 0 { c.NTPClient.AllowedNegativeDifference = new(time.Duration) *c.NTPClient.AllowedNegativeDifference = defaultNTPAllowedNegativeDifference } if len(c.NTPClient.Pool) < 1 { log.Warnln(log.ConfigMgr, "NTPClient enabled with no servers configured, enabling default pool.") c.NTPClient.Pool = []string{"pool.ntp.org:123"} } } // DisableNTPCheck allows the user to change how they are prompted for timesync alerts func (c *Config) DisableNTPCheck(input io.Reader) (string, error) { m.Lock() defer m.Unlock() reader := bufio.NewReader(input) log.Warnln(log.ConfigMgr, "Your system time is out of sync, this may cause issues with trading") log.Warnln(log.ConfigMgr, "How would you like to show future notifications? (a)lert / (w)arn / (d)isable") var resp string answered := false for !answered { answer, err := reader.ReadString('\n') if err != nil { return resp, err } answer = strings.TrimRight(answer, "\r\n") switch answer { case "a": c.NTPClient.Level = 0 resp = "Time sync has been set to alert" answered = true case "w": c.NTPClient.Level = 1 resp = "Time sync has been set to warn only" answered = true case "d": c.NTPClient.Level = -1 resp = "Future notifications for out of time sync has been disabled" answered = true default: log.Warnln(log.ConfigMgr, "Invalid option selected, please try again (a)lert / (w)arn / (d)isable") } } return resp, nil } // CheckConnectionMonitorConfig checks and if zero value assigns default values func (c *Config) CheckConnectionMonitorConfig() { m.Lock() defer m.Unlock() if c.ConnectionMonitor.CheckInterval == 0 { c.ConnectionMonitor.CheckInterval = connchecker.DefaultCheckInterval } if len(c.ConnectionMonitor.DNSList) == 0 { c.ConnectionMonitor.DNSList = connchecker.DefaultDNSList } if len(c.ConnectionMonitor.PublicDomainList) == 0 { c.ConnectionMonitor.PublicDomainList = connchecker.DefaultDomainList } } // GetFilePath returns the desired config file or the default config file name // based on if the application is being run under test or normal mode. func GetFilePath(configfile string) (string, error) { if configfile != "" { return configfile, nil } if flag.Lookup("test.v") != nil && !testBypass { return TestFile, nil } exePath, err := common.GetExecutablePath() if err != nil { return "", err } oldDirs := []string{ filepath.Join(exePath, File), filepath.Join(exePath, EncryptedFile), } newDir := common.GetDefaultDataDir(runtime.GOOS) err = common.CreateDir(newDir) if err != nil { return "", err } newDirs := []string{ filepath.Join(newDir, File), filepath.Join(newDir, EncryptedFile), } // First upgrade the old dir config file if it exists to the corresponding // new one for x := range oldDirs { _, err := os.Stat(oldDirs[x]) if os.IsNotExist(err) { continue } _, err = os.Stat(newDirs[x]) if !os.IsNotExist(err) { log.Warnf(log.ConfigMgr, "config.json file found in root dir and gct dir; cannot overwrite, defaulting to gct dir config.json at %s", newDirs[x]) return newDirs[x], nil } if filepath.Ext(oldDirs[x]) == ".json" { err = file.Move(oldDirs[x], newDirs[0]) if err != nil { return "", err } log.Debugf(log.ConfigMgr, "Renamed old config file %s to %s\n", oldDirs[x], newDirs[0]) } else { err = file.Move(oldDirs[x], newDirs[1]) if err != nil { return "", err } log.Debugf(log.ConfigMgr, "Renamed old config file %s to %s\n", oldDirs[x], newDirs[1]) } } // Secondly check to see if the new config file extension is correct or not for x := range newDirs { _, err := os.Stat(newDirs[x]) if os.IsNotExist(err) { continue } data, err := ioutil.ReadFile(newDirs[x]) if err != nil { return "", err } if ConfirmECS(data) { if filepath.Ext(newDirs[x]) == ".dat" { return newDirs[x], nil } err = file.Move(newDirs[x], newDirs[1]) if err != nil { return "", err } return newDirs[1], nil } if filepath.Ext(newDirs[x]) == ".json" { return newDirs[x], nil } err = file.Move(newDirs[x], newDirs[0]) if err != nil { return "", err } return newDirs[0], nil } return "", fmt.Errorf("config.json file not found in %s, please follow README.md in root dir for config generation", newDir) } // ReadConfig verifies and checks for encryption and verifies the unencrypted // file contains JSON. func (c *Config) ReadConfig(configPath string, dryrun bool) error { defaultPath, err := GetFilePath(configPath) if err != nil { return err } fileData, err := ioutil.ReadFile(defaultPath) if err != nil { return err } if !ConfirmECS(fileData) { err = ConfirmConfigJSON(fileData, &c) if err != nil { return err } if c.EncryptConfig == fileEncryptionDisabled { return nil } if c.EncryptConfig == fileEncryptionPrompt { m.Lock() IsInitialSetup = true m.Unlock() if c.PromptForConfigEncryption(configPath, dryrun) { c.EncryptConfig = fileEncryptionEnabled return c.SaveConfig(defaultPath, dryrun) } } } else { errCounter := 0 for { if errCounter >= maxAuthFailures { return errors.New("failed to decrypt config after 3 attempts") } key, err := PromptForConfigKey(IsInitialSetup) if err != nil { log.Errorf(log.ConfigMgr, "PromptForConfigKey err: %s", err) errCounter++ continue } var f []byte f = append(f, fileData...) data, err := DecryptConfigFile(f, key) if err != nil { log.Errorf(log.ConfigMgr, "DecryptConfigFile err: %s", err) errCounter++ continue } err = ConfirmConfigJSON(data, &c) if err != nil { if errCounter < maxAuthFailures { log.Error(log.ConfigMgr, "Invalid password.") } errCounter++ continue } break } } return nil } // SaveConfig saves your configuration to your desired path func (c *Config) SaveConfig(configPath string, dryrun bool) error { if dryrun { return nil } defaultPath, err := GetFilePath(configPath) if err != nil { return err } payload, err := json.MarshalIndent(c, "", " ") if err != nil { return err } if c.EncryptConfig == fileEncryptionEnabled { var key []byte if IsInitialSetup { key, err = PromptForConfigKey(true) if err != nil { return err } IsInitialSetup = false } payload, err = EncryptConfigFile(payload, key) if err != nil { return err } } return file.Write(defaultPath, payload) } // CheckRemoteControlConfig checks to see if the old c.Webserver field is used // and migrates the existing settings to the new RemoteControl struct func (c *Config) CheckRemoteControlConfig() { m.Lock() defer m.Unlock() if c.Webserver != nil { port := common.ExtractPort(c.Webserver.ListenAddress) host := common.ExtractHost(c.Webserver.ListenAddress) c.RemoteControl = RemoteControlConfig{ Username: c.Webserver.AdminUsername, Password: c.Webserver.AdminPassword, DeprecatedRPC: DepcrecatedRPCConfig{ Enabled: c.Webserver.Enabled, ListenAddress: host + ":" + strconv.Itoa(port), }, } port++ c.RemoteControl.WebsocketRPC = WebsocketRPCConfig{ Enabled: c.Webserver.Enabled, ListenAddress: host + ":" + strconv.Itoa(port), ConnectionLimit: c.Webserver.WebsocketConnectionLimit, MaxAuthFailures: c.Webserver.WebsocketMaxAuthFailures, AllowInsecureOrigin: c.Webserver.WebsocketAllowInsecureOrigin, } port++ gRPCProxyPort := port + 1 c.RemoteControl.GRPC = GRPCConfig{ Enabled: c.Webserver.Enabled, ListenAddress: host + ":" + strconv.Itoa(port), GRPCProxyEnabled: c.Webserver.Enabled, GRPCProxyListenAddress: host + ":" + strconv.Itoa(gRPCProxyPort), } // Then flush the old webserver settings c.Webserver = nil } } // CheckConfig checks all config settings func (c *Config) CheckConfig() error { err := c.CheckLoggerConfig() if err != nil { log.Errorf(log.ConfigMgr, "Failed to configure logger, some logging features unavailable: %s\n", err) } err = c.checkDatabaseConfig() if err != nil { log.Errorf(log.DatabaseMgr, "Failed to configure database: %v", err) } err = c.CheckExchangeConfigValues() if err != nil { return fmt.Errorf(ErrCheckingConfigValues, err) } c.CheckConnectionMonitorConfig() c.CheckCommunicationsConfig() c.CheckClientBankAccounts() c.CheckRemoteControlConfig() err = c.CheckCurrencyConfigValues() if err != nil { return err } if c.GlobalHTTPTimeout <= 0 { log.Warnf(log.ConfigMgr, "Global HTTP Timeout value not set, defaulting to %v.\n", defaultHTTPTimeout) c.GlobalHTTPTimeout = defaultHTTPTimeout } if c.NTPClient.Level != 0 { c.CheckNTPConfig() } return nil } // LoadConfig loads your configuration file into your configuration object func (c *Config) LoadConfig(configPath string, dryrun bool) error { err := c.ReadConfig(configPath, dryrun) if err != nil { return fmt.Errorf(ErrFailureOpeningConfig, configPath, err) } return c.CheckConfig() } // UpdateConfig updates the config with a supplied config file func (c *Config) UpdateConfig(configPath string, newCfg *Config, dryrun bool) error { err := newCfg.CheckConfig() if err != nil { return err } c.Name = newCfg.Name c.EncryptConfig = newCfg.EncryptConfig c.Currency = newCfg.Currency c.GlobalHTTPTimeout = newCfg.GlobalHTTPTimeout c.Portfolio = newCfg.Portfolio c.Communications = newCfg.Communications c.Webserver = newCfg.Webserver c.Exchanges = newCfg.Exchanges err = c.SaveConfig(configPath, dryrun) if err != nil { return err } return c.LoadConfig(configPath, dryrun) } // GetConfig returns a pointer to a configuration object func GetConfig() *Config { return &Cfg }