master
go 339 lines 9.5 KB
Raw
1 package main
2
3 import (
4 "context"
5 "flag"
6 "fmt"
7 "io"
8 "log"
9 "os"
10 "path/filepath"
11 "strings"
12 "sync"
13 "time"
14
15 "github.com/ipfs/boxo/files"
16 "github.com/ipfs/boxo/path"
17 icore "github.com/ipfs/kubo/core/coreiface"
18 options "github.com/ipfs/kubo/core/coreiface/options"
19 ma "github.com/multiformats/go-multiaddr"
20
21 "github.com/ipfs/kubo/config"
22 "github.com/ipfs/kubo/core"
23 "github.com/ipfs/kubo/core/coreapi"
24 "github.com/ipfs/kubo/core/node/libp2p"
25 "github.com/ipfs/kubo/plugin/loader" // registers built-in plugins
26 "github.com/ipfs/kubo/repo/fsrepo"
27 "github.com/libp2p/go-libp2p/core/peer"
28 )
29
30 /// ------ Setting up the IPFS Repo
31
32 func setupPlugins(externalPluginsPath string) error {
33 plugins, err := loader.NewPluginLoader(filepath.Join(externalPluginsPath, "plugins"))
34 if err != nil {
35 return fmt.Errorf("error loading plugins: %s", err)
36 }
37
38 if err := plugins.Initialize(); err != nil {
39 return fmt.Errorf("error initializing plugins: %s", err)
40 }
41
42 if err := plugins.Inject(); err != nil {
43 return fmt.Errorf("error initializing plugins: %s", err)
44 }
45
46 return nil
47 }
48
49 func createTempRepo() (string, error) {
50 repoPath, err := os.MkdirTemp("", "ipfs-shell")
51 if err != nil {
52 return "", fmt.Errorf("failed to get temp dir: %s", err)
53 }
54
55 identity, err := config.CreateIdentity(io.Discard, []options.KeyGenerateOption{
56 options.Key.Type(options.Ed25519Key),
57 })
58 if err != nil {
59 return "", err
60 }
61 cfg, err := config.InitWithIdentity(identity)
62 if err != nil {
63 return "", err
64 }
65
66 // TCP on loopback with a random port. QUIC/UDP is disabled because it can
67 // be throttled on some networks; TCP is more reliable for local testing.
68 cfg.Addresses.Swarm = []string{
69 "/ip4/127.0.0.1/tcp/0",
70 }
71 cfg.Swarm.Transports.Network.QUIC = config.False
72 cfg.Swarm.Transports.Network.Relay = config.False
73 cfg.Swarm.Transports.Network.WebTransport = config.False
74 cfg.Swarm.Transports.Network.WebRTCDirect = config.False
75 cfg.Swarm.Transports.Network.Websocket = config.False
76 cfg.AutoTLS.Enabled = config.False
77
78 // No DHT: we connect peers by address, so content routing is not needed.
79 cfg.Routing.Type = config.NewOptionalString("none")
80
81 // No automatic bootstrap: we connect only the peers we need.
82 cfg.Bootstrap = []string{}
83
84 // Optional: enable experimental features by modifying cfg before Init, e.g.:
85 if *flagExp {
86 // https://github.com/ipfs/kubo/blob/master/docs/experimental-features.md#ipfs-filestore
87 cfg.Experimental.FilestoreEnabled = true
88 // https://github.com/ipfs/kubo/blob/master/docs/experimental-features.md#ipfs-urlstore
89 cfg.Experimental.UrlstoreEnabled = true
90 // https://github.com/ipfs/kubo/blob/master/docs/experimental-features.md#ipfs-p2p
91 cfg.Experimental.Libp2pStreamMounting = true
92 // https://github.com/ipfs/kubo/blob/master/docs/experimental-features.md#p2p-http-proxy
93 cfg.Experimental.P2pHttpProxy = true
94 // See also: https://github.com/ipfs/kubo/blob/master/docs/config.md
95 }
96
97 err = fsrepo.Init(repoPath, cfg)
98 if err != nil {
99 return "", fmt.Errorf("failed to init ephemeral node: %s", err)
100 }
101
102 return repoPath, nil
103 }
104
105 /// ------ Spawning the node
106
107 // createNode opens the repo at repoPath and starts an IPFS node.
108 func createNode(ctx context.Context, repoPath string) (*core.IpfsNode, error) {
109 repo, err := fsrepo.Open(repoPath)
110 if err != nil {
111 return nil, err
112 }
113
114 nodeOptions := &core.BuildCfg{
115 Online: true,
116 // No routing: peers are connected directly by address.
117 // In production use libp2p.DHTClientOption or libp2p.DHTOption
118 // so the node can find content and peers on the wider network.
119 Routing: libp2p.NilRouterOption,
120 Repo: repo,
121 }
122
123 return core.NewNode(ctx, nodeOptions)
124 }
125
126 var loadPluginsOnce sync.Once
127
128 // spawnEphemeral creates a temporary repo, starts a node, and returns its API.
129 func spawnEphemeral(ctx context.Context) (icore.CoreAPI, *core.IpfsNode, error) {
130 var onceErr error
131 loadPluginsOnce.Do(func() {
132 onceErr = setupPlugins("")
133 })
134 if onceErr != nil {
135 return nil, nil, onceErr
136 }
137
138 repoPath, err := createTempRepo()
139 if err != nil {
140 return nil, nil, fmt.Errorf("failed to create temp repo: %s", err)
141 }
142
143 node, err := createNode(ctx, repoPath)
144 if err != nil {
145 return nil, nil, err
146 }
147
148 api, err := coreapi.NewCoreAPI(node)
149
150 return api, node, err
151 }
152
153 func connectToPeers(ctx context.Context, ipfs icore.CoreAPI, peers []string) error {
154 var wg sync.WaitGroup
155 peerInfos := make(map[peer.ID]*peer.AddrInfo, len(peers))
156 for _, addrStr := range peers {
157 addr, err := ma.NewMultiaddr(addrStr)
158 if err != nil {
159 return err
160 }
161 pii, err := peer.AddrInfoFromP2pAddr(addr)
162 if err != nil {
163 return err
164 }
165 pi, ok := peerInfos[pii.ID]
166 if !ok {
167 pi = &peer.AddrInfo{ID: pii.ID}
168 peerInfos[pi.ID] = pi
169 }
170 pi.Addrs = append(pi.Addrs, pii.Addrs...)
171 }
172
173 wg.Add(len(peerInfos))
174 for _, peerInfo := range peerInfos {
175 go func(peerInfo *peer.AddrInfo) {
176 defer wg.Done()
177 err := ipfs.Swarm().Connect(ctx, *peerInfo)
178 if err != nil {
179 log.Printf("failed to connect to %s: %s", peerInfo.ID, err)
180 }
181 }(peerInfo)
182 }
183 wg.Wait()
184 return nil
185 }
186
187 func getUnixfsNode(path string) (files.Node, error) {
188 st, err := os.Stat(path)
189 if err != nil {
190 return nil, err
191 }
192
193 f, err := files.NewSerialFile(path, false, st)
194 if err != nil {
195 return nil, err
196 }
197
198 return f, nil
199 }
200
201 /// -------
202
203 var flagExp = flag.Bool("experimental", false, "enable experimental features")
204
205 func main() {
206 flag.Parse()
207
208 /// --- Part I: Getting a IPFS node running
209
210 fmt.Println("-- Getting an IPFS node running -- ")
211
212 ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute)
213 defer cancel()
214
215 // Spawn a local peer using a temporary path, for testing purposes.
216 ipfsA, nodeA, err := spawnEphemeral(ctx)
217 if err != nil {
218 panic(fmt.Errorf("failed to spawn peer node: %s", err))
219 }
220
221 fmt.Println("Spawning Kubo node on a temporary repo")
222 ipfsB, _, err := spawnEphemeral(ctx)
223 if err != nil {
224 panic(fmt.Errorf("failed to spawn ephemeral node: %s", err))
225 }
226
227 fmt.Println("IPFS node is running")
228
229 // Connect nodeB to nodeA before adding content. This lets the connection
230 // finish its setup during the Add below, so the fetch in Part IV is fast.
231 peerAddrs, err := ipfsA.Swarm().LocalAddrs(ctx)
232 if err != nil {
233 panic(fmt.Errorf("could not get peer addresses: %s", err))
234 }
235 peerMa := peerAddrs[0].String() + "/p2p/" + nodeA.Identity.String()
236 fmt.Println("Connecting to peer...")
237 if err := connectToPeers(ctx, ipfsB, []string{peerMa}); err != nil {
238 panic(fmt.Errorf("failed to connect to peer: %s", err))
239 }
240 fmt.Println("Connected to peer")
241
242 peerCidFile, err := ipfsA.Unixfs().Add(ctx,
243 files.NewBytesFile([]byte("hello from ipfs 101 in Kubo")))
244 if err != nil {
245 panic(fmt.Errorf("could not add File: %s", err))
246 }
247
248 fmt.Printf("Added file to peer with CID %s\n", peerCidFile.String())
249
250 /// --- Part II: Adding a file and a directory to IPFS
251
252 fmt.Println("\n-- Adding and getting back files & directories --")
253
254 inputBasePath := "../example-folder/"
255 inputPathFile := inputBasePath + "ipfs.paper.draft3.pdf"
256 inputPathDirectory := inputBasePath + "test-dir"
257
258 someFile, err := getUnixfsNode(inputPathFile)
259 if err != nil {
260 panic(fmt.Errorf("could not get File: %s", err))
261 }
262
263 cidFile, err := ipfsB.Unixfs().Add(ctx, someFile)
264 if err != nil {
265 panic(fmt.Errorf("could not add File: %s", err))
266 }
267
268 fmt.Printf("Added file to IPFS with CID %s\n", cidFile.String())
269
270 someDirectory, err := getUnixfsNode(inputPathDirectory)
271 if err != nil {
272 panic(fmt.Errorf("could not get File: %s", err))
273 }
274
275 cidDirectory, err := ipfsB.Unixfs().Add(ctx, someDirectory)
276 if err != nil {
277 panic(fmt.Errorf("could not add Directory: %s", err))
278 }
279
280 fmt.Printf("Added directory to IPFS with CID %s\n", cidDirectory.String())
281
282 /// --- Part III: Getting the file and directory you added back
283
284 outputBasePath, err := os.MkdirTemp("", "example")
285 if err != nil {
286 panic(fmt.Errorf("could not create output dir (%v)", err))
287 }
288 fmt.Printf("output folder: %s\n", outputBasePath)
289 outputPathFile := outputBasePath + strings.Split(cidFile.String(), "/")[2]
290 outputPathDirectory := outputBasePath + strings.Split(cidDirectory.String(), "/")[2]
291
292 rootNodeFile, err := ipfsB.Unixfs().Get(ctx, cidFile)
293 if err != nil {
294 panic(fmt.Errorf("could not get file with CID: %s", err))
295 }
296
297 err = files.WriteTo(rootNodeFile, outputPathFile)
298 if err != nil {
299 panic(fmt.Errorf("could not write out the fetched CID: %s", err))
300 }
301
302 fmt.Printf("got file back from IPFS (IPFS path: %s) and wrote it to %s\n", cidFile.String(), outputPathFile)
303
304 rootNodeDirectory, err := ipfsB.Unixfs().Get(ctx, cidDirectory)
305 if err != nil {
306 panic(fmt.Errorf("could not get file with CID: %s", err))
307 }
308
309 err = files.WriteTo(rootNodeDirectory, outputPathDirectory)
310 if err != nil {
311 panic(fmt.Errorf("could not write out the fetched CID: %s", err))
312 }
313
314 fmt.Printf("Got directory back from IPFS (IPFS path: %s) and wrote it to %s\n", cidDirectory.String(), outputPathDirectory)
315
316 /// --- Part IV: Getting a file from another IPFS node
317
318 fmt.Println("\n-- Fetching content from nodeA via bitswap --")
319
320 exampleCIDStr := peerCidFile.RootCid().String()
321
322 fmt.Printf("Fetching a file from the network with CID %s\n", exampleCIDStr)
323 outputPath := outputBasePath + exampleCIDStr
324 testCID := path.FromCid(peerCidFile.RootCid())
325
326 rootNode, err := ipfsB.Unixfs().Get(ctx, testCID)
327 if err != nil {
328 panic(fmt.Errorf("could not get file with CID: %s", err))
329 }
330
331 err = files.WriteTo(rootNode, outputPath)
332 if err != nil {
333 panic(fmt.Errorf("could not write out the fetched CID: %s", err))
334 }
335
336 fmt.Printf("Wrote the file to %s\n", outputPath)
337
338 fmt.Println("\nAll done! You just finalized your first tutorial on how to use Kubo as a library")
339 }