master
go 128 lines 4.23 KB
Raw
1 package config
2
3 import (
4 "fmt"
5 "path/filepath"
6 "slices"
7 "sync"
8
9 "github.com/ipfs/boxo/autoconf"
10 logging "github.com/ipfs/go-log/v2"
11 version "github.com/ipfs/kubo"
12 )
13
14 var autoconfLog = logging.Logger("autoconf")
15
16 // Singleton state for autoconf client
17 var (
18 clientOnce sync.Once
19 clientCache *autoconf.Client
20 clientErr error
21 )
22
23 // GetAutoConfClient returns a cached autoconf client or creates a new one.
24 // This is thread-safe and uses a singleton pattern.
25 func GetAutoConfClient(cfg *Config) (*autoconf.Client, error) {
26 clientOnce.Do(func() {
27 clientCache, clientErr = newAutoConfClient(cfg)
28 })
29 return clientCache, clientErr
30 }
31
32 // newAutoConfClient creates a new autoconf client with the given config
33 func newAutoConfClient(cfg *Config) (*autoconf.Client, error) {
34 // Get repo path for cache directory
35 repoPath, err := PathRoot()
36 if err != nil {
37 return nil, fmt.Errorf("failed to get repo path: %w", err)
38 }
39
40 // Prepare refresh interval with nil check
41 refreshInterval := cfg.AutoConf.RefreshInterval
42 if refreshInterval == nil {
43 refreshInterval = &OptionalDuration{}
44 }
45
46 // Use default URL if not specified
47 url := cfg.AutoConf.URL.WithDefault(DefaultAutoConfURL)
48
49 // Build client options
50 options := []autoconf.Option{
51 autoconf.WithCacheDir(filepath.Join(repoPath, "autoconf")),
52 autoconf.WithUserAgent(version.GetUserAgentVersion()),
53 autoconf.WithCacheSize(DefaultAutoConfCacheSize),
54 autoconf.WithTimeout(DefaultAutoConfTimeout),
55 autoconf.WithRefreshInterval(refreshInterval.WithDefault(DefaultAutoConfRefreshInterval)),
56 autoconf.WithFallback(autoconf.GetMainnetFallbackConfig),
57 autoconf.WithURL(url),
58 }
59
60 return autoconf.NewClient(options...)
61 }
62
63 // ValidateAutoConfWithRepo validates that autoconf setup is correct at daemon startup with repo access
64 func ValidateAutoConfWithRepo(cfg *Config, swarmKeyExists bool) error {
65 if !cfg.AutoConf.Enabled.WithDefault(DefaultAutoConfEnabled) {
66 // AutoConf is disabled, check for "auto" values and warn
67 return validateAutoConfDisabled(cfg)
68 }
69
70 // Check for private network with default mainnet URL
71 url := cfg.AutoConf.URL.WithDefault(DefaultAutoConfURL)
72 if swarmKeyExists && url == DefaultAutoConfURL {
73 return fmt.Errorf("AutoConf cannot use the default mainnet URL (%s) on a private network (swarm.key or LIBP2P_FORCE_PNET detected). Either disable AutoConf by setting AutoConf.Enabled=false, or configure AutoConf.URL to point to a configuration service specific to your private swarm", DefaultAutoConfURL)
74 }
75
76 // Further validation will happen lazily when config is accessed
77 return nil
78 }
79
80 // validateAutoConfDisabled checks for "auto" values when AutoConf is disabled and logs errors
81 func validateAutoConfDisabled(cfg *Config) error {
82 hasAutoValues := false
83 var errors []string
84
85 // Check Bootstrap
86 if slices.Contains(cfg.Bootstrap, AutoPlaceholder) {
87 hasAutoValues = true
88 errors = append(errors, "Bootstrap contains 'auto' but AutoConf.Enabled=false")
89 }
90
91 // Check DNS.Resolvers
92 if cfg.DNS.Resolvers != nil {
93 for _, resolver := range cfg.DNS.Resolvers {
94 if resolver == AutoPlaceholder {
95 hasAutoValues = true
96 errors = append(errors, "DNS.Resolvers contains 'auto' but AutoConf.Enabled=false")
97 break
98 }
99 }
100 }
101
102 // Check Routing.DelegatedRouters
103 if slices.Contains(cfg.Routing.DelegatedRouters, AutoPlaceholder) {
104 hasAutoValues = true
105 errors = append(errors, "Routing.DelegatedRouters contains 'auto' but AutoConf.Enabled=false")
106 }
107
108 // Check Ipns.DelegatedPublishers
109 if slices.Contains(cfg.Ipns.DelegatedPublishers, AutoPlaceholder) {
110 hasAutoValues = true
111 errors = append(errors, "Ipns.DelegatedPublishers contains 'auto' but AutoConf.Enabled=false")
112 }
113
114 // Log all errors
115 for _, errMsg := range errors {
116 autoconfLog.Error(errMsg)
117 }
118
119 // If only auto values exist and no static ones, fail to start
120 if hasAutoValues {
121 if len(cfg.Bootstrap) == 1 && cfg.Bootstrap[0] == AutoPlaceholder {
122 autoconfLog.Error("Kubo cannot start with only 'auto' Bootstrap values when AutoConf.Enabled=false")
123 return fmt.Errorf("no usable bootstrap peers: AutoConf is disabled (AutoConf.Enabled=false) but 'auto' placeholder is used in Bootstrap config. Either set AutoConf.Enabled=true to enable automatic configuration, or replace 'auto' with specific Bootstrap peer addresses")
124 }
125 }
126
127 return nil
128 }