LoadConfigOptional reads YAML from configFile. If optional is true and the file is missing, it returns an empty Config. If optional is true and the file is empty or invalid, it returns an empty Config.
(configFile string, optional bool)
| 729 | // If optional is true and the file is missing, it returns an empty Config. |
| 730 | // If optional is true and the file is empty or invalid, it returns an empty Config. |
| 731 | func LoadConfigOptional(configFile string, optional bool) (*Config, error) { |
| 732 | // Read the entire configuration file into memory. |
| 733 | data, err := os.ReadFile(configFile) |
| 734 | if err != nil { |
| 735 | if optional { |
| 736 | if os.IsNotExist(err) || errors.Is(err, syscall.EISDIR) { |
| 737 | // Missing and optional: return empty config (cloud deploy standby). |
| 738 | cfg := &Config{} |
| 739 | cfg.NormalizePluginsConfig() |
| 740 | return cfg, nil |
| 741 | } |
| 742 | } |
| 743 | return nil, fmt.Errorf("failed to read config file: %w", err) |
| 744 | } |
| 745 | |
| 746 | // In cloud deploy mode (optional=true), if file is empty or contains only whitespace, return empty config. |
| 747 | if optional && len(data) == 0 { |
| 748 | cfg := &Config{} |
| 749 | cfg.NormalizePluginsConfig() |
| 750 | return cfg, nil |
| 751 | } |
| 752 | |
| 753 | // Unmarshal the YAML data into the Config struct. |
| 754 | var cfg Config |
| 755 | // Set defaults before unmarshal so that absent keys keep defaults. |
| 756 | cfg.Host = "" // Default empty: binds to all interfaces (IPv4 + IPv6) |
| 757 | cfg.LoggingToFile = false |
| 758 | cfg.LogsMaxTotalSizeMB = 0 |
| 759 | cfg.ErrorLogsMaxFiles = 10 |
| 760 | cfg.UsageStatisticsEnabled = false |
| 761 | cfg.RedisUsageQueueRetentionSeconds = 60 |
| 762 | cfg.DisableCooling = false |
| 763 | cfg.SaveCooldownStatus = false |
| 764 | cfg.TransientErrorCooldownSeconds = 0 |
| 765 | cfg.DisableImageGeneration = DisableImageGenerationOff |
| 766 | cfg.Pprof.Enable = false |
| 767 | cfg.Pprof.Addr = DefaultPprofAddr |
| 768 | cfg.RemoteManagement.PanelGitHubRepository = DefaultPanelGitHubRepository |
| 769 | if err = yaml.Unmarshal(data, &cfg); err != nil { |
| 770 | if optional { |
| 771 | // In cloud deploy mode, if YAML parsing fails, return empty config instead of error. |
| 772 | cfgOptional := &Config{} |
| 773 | cfgOptional.NormalizePluginsConfig() |
| 774 | return cfgOptional, nil |
| 775 | } |
| 776 | return nil, fmt.Errorf("failed to parse config file: %w", err) |
| 777 | } |
| 778 | |
| 779 | // Hash remote management key if plaintext is detected (nested) |
| 780 | // We consider a value to be already hashed if it looks like a bcrypt hash ($2a$, $2b$, or $2y$ prefix). |
| 781 | if cfg.RemoteManagement.SecretKey != "" && !looksLikeBcrypt(cfg.RemoteManagement.SecretKey) { |
| 782 | hashed, errHash := hashSecret(cfg.RemoteManagement.SecretKey) |
| 783 | if errHash != nil { |
| 784 | return nil, fmt.Errorf("failed to hash remote management key: %w", errHash) |
| 785 | } |
| 786 | cfg.RemoteManagement.SecretKey = hashed |
| 787 | |
| 788 | // Persist the hashed value back to the config file to avoid re-hashing on next startup. |