| 1 | // package mg16 contains the code to perform 16-17 repository migration in Kubo. |
| 2 | // This handles the following: |
| 3 | // - Migrate default bootstrap peers to "auto" |
| 4 | // - Migrate DNS resolvers to use "auto" for "." eTLD |
| 5 | // - Enable AutoConf system with default settings |
| 6 | // - Increment repo version to 17 |
| 7 | package mg16 |
| 8 | |
| 9 | import ( |
| 10 | "io" |
| 11 | "slices" |
| 12 | |
| 13 | "github.com/ipfs/kubo/config" |
| 14 | "github.com/ipfs/kubo/repo/fsrepo/migrations/common" |
| 15 | ) |
| 16 | |
| 17 | // DefaultBootstrapAddresses are the hardcoded bootstrap addresses from Kubo 0.36 |
| 18 | // for IPFS. they are nodes run by the IPFS team. docs on these later. |
| 19 | // As with all p2p networks, bootstrap is an important security concern. |
| 20 | // This list is used during migration to detect which peers are defaults vs custom. |
| 21 | var DefaultBootstrapAddresses = []string{ |
| 22 | "/dnsaddr/bootstrap.libp2p.io/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN", |
| 23 | "/dnsaddr/bootstrap.libp2p.io/p2p/QmQCU2EcMqAqQPR2i9bChDtGNJchTbq5TbXJJ16u19uLTa", // rust-libp2p-server |
| 24 | "/dnsaddr/bootstrap.libp2p.io/p2p/QmbLHAnMoJPWSCR5Zhtx6BHJX9KiKNN6tpvbUcqanj75Nb", |
| 25 | "/dnsaddr/bootstrap.libp2p.io/p2p/QmcZf59bWwK5XFi76CZX8cbJ4BhTzzA3gU1ZjYZcYW3dwt", |
| 26 | "/dnsaddr/va1.bootstrap.libp2p.io/p2p/12D3KooWKnDdG3iXw9eTFijk3EWSunZcFi54Zka4wmtqtt6rPxc8", // js-libp2p-amino-dht-bootstrapper |
| 27 | "/ip4/104.131.131.82/tcp/4001/p2p/QmaCpDMGvV2BGHeYERUEnRQAwe3N8SzbUtfsmvsqQLuvuJ", // mars.i.ipfs.io |
| 28 | "/ip4/104.131.131.82/udp/4001/quic-v1/p2p/QmaCpDMGvV2BGHeYERUEnRQAwe3N8SzbUtfsmvsqQLuvuJ", // mars.i.ipfs.io |
| 29 | } |
| 30 | |
| 31 | // Migration is the main exported migration for 16-to-17 |
| 32 | var Migration = &common.BaseMigration{ |
| 33 | FromVersion: "16", |
| 34 | ToVersion: "17", |
| 35 | Description: "Upgrading config to use AutoConf system", |
| 36 | Convert: convert, |
| 37 | } |
| 38 | |
| 39 | // NewMigration creates a new migration instance (for compatibility) |
| 40 | func NewMigration() common.Migration { |
| 41 | return Migration |
| 42 | } |
| 43 | |
| 44 | // convert converts the config from version 16 to 17 |
| 45 | func convert(in io.ReadSeeker, out io.Writer) error { |
| 46 | confMap, err := common.ReadConfig(in) |
| 47 | if err != nil { |
| 48 | return err |
| 49 | } |
| 50 | |
| 51 | // Enable AutoConf system |
| 52 | if err := enableAutoConf(confMap); err != nil { |
| 53 | return err |
| 54 | } |
| 55 | |
| 56 | // Migrate Bootstrap peers |
| 57 | if err := migrateBootstrap(confMap); err != nil { |
| 58 | return err |
| 59 | } |
| 60 | |
| 61 | // Migrate DNS resolvers |
| 62 | if err := migrateDNSResolvers(confMap); err != nil { |
| 63 | return err |
| 64 | } |
| 65 | |
| 66 | // Migrate DelegatedRouters |
| 67 | if err := migrateDelegatedRouters(confMap); err != nil { |
| 68 | return err |
| 69 | } |
| 70 | |
| 71 | // Migrate DelegatedPublishers |
| 72 | if err := migrateDelegatedPublishers(confMap); err != nil { |
| 73 | return err |
| 74 | } |
| 75 | |
| 76 | // Save new config |
| 77 | return common.WriteConfig(out, confMap) |
| 78 | } |
| 79 | |
| 80 | // enableAutoConf adds AutoConf section to config |
| 81 | func enableAutoConf(confMap map[string]any) error { |
| 82 | // Add empty AutoConf section if it doesn't exist - all fields will use implicit defaults: |
| 83 | // - Enabled defaults to true (via DefaultAutoConfEnabled) |
| 84 | // - URL defaults to mainnet URL (via DefaultAutoConfURL) |
| 85 | // - RefreshInterval defaults to 24h (via DefaultAutoConfRefreshInterval) |
| 86 | // - TLSInsecureSkipVerify defaults to false (no WithDefault, but false is zero value) |
| 87 | common.SetDefault(confMap, "AutoConf", map[string]any{}) |
| 88 | return nil |
| 89 | } |
| 90 | |
| 91 | // migrateBootstrap migrates bootstrap peers to use "auto" |
| 92 | func migrateBootstrap(confMap map[string]any) error { |
| 93 | bootstrap, exists := confMap["Bootstrap"] |
| 94 | if !exists { |
| 95 | // No bootstrap section, add "auto" |
| 96 | confMap["Bootstrap"] = []string{config.AutoPlaceholder} |
| 97 | return nil |
| 98 | } |
| 99 | |
| 100 | // Convert to string slice using helper |
| 101 | bootstrapPeers := common.ConvertInterfaceSlice(common.SafeCastSlice(bootstrap)) |
| 102 | if len(bootstrapPeers) == 0 && bootstrap != nil { |
| 103 | // Invalid bootstrap format, replace with "auto" |
| 104 | confMap["Bootstrap"] = []string{config.AutoPlaceholder} |
| 105 | return nil |
| 106 | } |
| 107 | |
| 108 | // Process bootstrap peers according to migration rules |
| 109 | newBootstrap := processBootstrapPeers(bootstrapPeers) |
| 110 | confMap["Bootstrap"] = newBootstrap |
| 111 | |
| 112 | return nil |
| 113 | } |
| 114 | |
| 115 | // processBootstrapPeers processes bootstrap peers according to migration rules |
| 116 | func processBootstrapPeers(peers []string) []string { |
| 117 | // If empty, use "auto" |
| 118 | if len(peers) == 0 { |
| 119 | return []string{config.AutoPlaceholder} |
| 120 | } |
| 121 | |
| 122 | // Filter out default peers to get only custom ones |
| 123 | customPeers := slices.DeleteFunc(slices.Clone(peers), func(peer string) bool { |
| 124 | return slices.Contains(DefaultBootstrapAddresses, peer) |
| 125 | }) |
| 126 | |
| 127 | // Check if any default peers were removed |
| 128 | hasDefaultPeers := len(customPeers) < len(peers) |
| 129 | |
| 130 | // If we have default peers, replace them with "auto" |
| 131 | if hasDefaultPeers { |
| 132 | return append([]string{config.AutoPlaceholder}, customPeers...) |
| 133 | } |
| 134 | |
| 135 | // No default peers found, keep as is |
| 136 | return peers |
| 137 | } |
| 138 | |
| 139 | // migrateDNSResolvers migrates DNS resolvers to use "auto" for "." eTLD |
| 140 | func migrateDNSResolvers(confMap map[string]any) error { |
| 141 | // Get or create DNS section |
| 142 | dns := common.GetOrCreateSection(confMap, "DNS") |
| 143 | |
| 144 | // Get existing resolvers or create empty map |
| 145 | resolvers := common.SafeCastMap(dns["Resolvers"]) |
| 146 | |
| 147 | // Define default resolvers that should be replaced with "auto" |
| 148 | defaultResolvers := map[string]string{ |
| 149 | "https://dns.eth.limo/dns-query": config.AutoPlaceholder, |
| 150 | "https://dns.eth.link/dns-query": config.AutoPlaceholder, |
| 151 | "https://resolver.cloudflare-eth.com/dns-query": config.AutoPlaceholder, |
| 152 | } |
| 153 | |
| 154 | // Replace default resolvers with "auto" |
| 155 | stringResolvers := common.ReplaceDefaultsWithAuto(resolvers, defaultResolvers) |
| 156 | |
| 157 | // Ensure "." is set to "auto" if not already set |
| 158 | if _, exists := stringResolvers["."]; !exists { |
| 159 | stringResolvers["."] = config.AutoPlaceholder |
| 160 | } |
| 161 | |
| 162 | dns["Resolvers"] = stringResolvers |
| 163 | return nil |
| 164 | } |
| 165 | |
| 166 | // migrateDelegatedRouters migrates DelegatedRouters to use "auto" |
| 167 | func migrateDelegatedRouters(confMap map[string]any) error { |
| 168 | // Get or create Routing section |
| 169 | routing := common.GetOrCreateSection(confMap, "Routing") |
| 170 | |
| 171 | // Get existing delegated routers |
| 172 | delegatedRouters, exists := routing["DelegatedRouters"] |
| 173 | |
| 174 | // Check if it's empty or nil |
| 175 | if !exists || common.IsEmptySlice(delegatedRouters) { |
| 176 | routing["DelegatedRouters"] = []string{config.AutoPlaceholder} |
| 177 | return nil |
| 178 | } |
| 179 | |
| 180 | // Process the list to replace cid.contact with "auto" and preserve others |
| 181 | routers := common.ConvertInterfaceSlice(common.SafeCastSlice(delegatedRouters)) |
| 182 | var newRouters []string |
| 183 | hasAuto := false |
| 184 | |
| 185 | for _, router := range routers { |
| 186 | if router == "https://cid.contact" { |
| 187 | if !hasAuto { |
| 188 | newRouters = append(newRouters, config.AutoPlaceholder) |
| 189 | hasAuto = true |
| 190 | } |
| 191 | } else { |
| 192 | newRouters = append(newRouters, router) |
| 193 | } |
| 194 | } |
| 195 | |
| 196 | // If empty after processing, add "auto" |
| 197 | if len(newRouters) == 0 { |
| 198 | newRouters = []string{config.AutoPlaceholder} |
| 199 | } |
| 200 | |
| 201 | routing["DelegatedRouters"] = newRouters |
| 202 | return nil |
| 203 | } |
| 204 | |
| 205 | // migrateDelegatedPublishers migrates DelegatedPublishers to use "auto" |
| 206 | func migrateDelegatedPublishers(confMap map[string]any) error { |
| 207 | // Get or create Ipns section |
| 208 | ipns := common.GetOrCreateSection(confMap, "Ipns") |
| 209 | |
| 210 | // Get existing delegated publishers |
| 211 | delegatedPublishers, exists := ipns["DelegatedPublishers"] |
| 212 | |
| 213 | // Check if it's empty or nil - only then replace with "auto" |
| 214 | // Otherwise preserve custom publishers |
| 215 | if !exists || common.IsEmptySlice(delegatedPublishers) { |
| 216 | ipns["DelegatedPublishers"] = []string{config.AutoPlaceholder} |
| 217 | } |
| 218 | // If there are custom publishers, leave them as is |
| 219 | |
| 220 | return nil |
| 221 | } |