Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
104 changes: 42 additions & 62 deletions docs/examples/kubo-as-a-library/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,27 +15,26 @@ import (
"github.com/ipfs/boxo/files"
"github.com/ipfs/boxo/path"
icore "github.com/ipfs/kubo/core/coreiface"
options "github.com/ipfs/kubo/core/coreiface/options"
ma "github.com/multiformats/go-multiaddr"

"github.com/ipfs/kubo/config"
"github.com/ipfs/kubo/core"
"github.com/ipfs/kubo/core/coreapi"
"github.com/ipfs/kubo/core/node/libp2p"
"github.com/ipfs/kubo/plugin/loader" // This package is needed so that all the preloaded plugins are loaded automatically
"github.com/ipfs/kubo/plugin/loader" // registers built-in plugins
"github.com/ipfs/kubo/repo/fsrepo"
"github.com/libp2p/go-libp2p/core/peer"
)

/// ------ Setting up the IPFS Repo

func setupPlugins(externalPluginsPath string) error {
// Load any external plugins if available on externalPluginsPath
plugins, err := loader.NewPluginLoader(filepath.Join(externalPluginsPath, "plugins"))
if err != nil {
return fmt.Errorf("error loading plugins: %s", err)
}

// Load preloaded and external plugins
if err := plugins.Initialize(); err != nil {
return fmt.Errorf("error initializing plugins: %s", err)
}
Expand All @@ -53,37 +52,36 @@ func createTempRepo() (string, error) {
return "", fmt.Errorf("failed to get temp dir: %s", err)
}

// Create a config with default options and a 2048 bit key
cfg, err := config.Init(io.Discard, 2048)
identity, err := config.CreateIdentity(io.Discard, []options.KeyGenerateOption{
options.Key.Type(options.Ed25519Key),
})
if err != nil {
return "", err
}
cfg, err := config.InitWithIdentity(identity)
if err != nil {
return "", err
}

// Use TCP-only on loopback with random port for reliable local testing.
// This matches what kubo's test harness uses (test/cli/transports_test.go).
// QUIC/UDP transports are avoided because they may be throttled on CI.
// TCP on loopback with a random port. QUIC/UDP is disabled because it can
// be throttled on some networks; TCP is more reliable for local testing.
cfg.Addresses.Swarm = []string{
"/ip4/127.0.0.1/tcp/0",
}

// Explicitly disable non-TCP transports for reliability.
cfg.Swarm.Transports.Network.QUIC = config.False
cfg.Swarm.Transports.Network.Relay = config.False
cfg.Swarm.Transports.Network.WebTransport = config.False
cfg.Swarm.Transports.Network.WebRTCDirect = config.False
cfg.Swarm.Transports.Network.Websocket = config.False
cfg.AutoTLS.Enabled = config.False

// Disable routing - we don't need DHT for direct peer connections.
// Bitswap works with directly connected peers without needing DHT lookups.
// No DHT: we connect peers by address, so content routing is not needed.
cfg.Routing.Type = config.NewOptionalString("none")

// Disable bootstrap for this example - we manually connect only the peers we need.
// No automatic bootstrap: we connect only the peers we need.
cfg.Bootstrap = []string{}

// When creating the repository, you can define custom settings on the repository, such as enabling experimental
// features (See experimental-features.md) or customizing the gateway endpoint.
// To do such things, you should modify the variable `cfg`. For example:
// Optional: enable experimental features by modifying cfg before Init, e.g.:
if *flagExp {
// https://github.com/ipfs/kubo/blob/master/docs/experimental-features.md#ipfs-filestore
cfg.Experimental.FilestoreEnabled = true
Expand All @@ -94,10 +92,8 @@ func createTempRepo() (string, error) {
// https://github.com/ipfs/kubo/blob/master/docs/experimental-features.md#p2p-http-proxy
cfg.Experimental.P2pHttpProxy = true
// See also: https://github.com/ipfs/kubo/blob/master/docs/config.md
// And: https://github.com/ipfs/kubo/blob/master/docs/experimental-features.md
}

// Create the repo with the config
err = fsrepo.Init(repoPath, cfg)
if err != nil {
return "", fmt.Errorf("failed to init ephemeral node: %s", err)
Expand All @@ -108,23 +104,18 @@ func createTempRepo() (string, error) {

/// ------ Spawning the node

// Creates an IPFS node and returns its coreAPI.
// createNode opens the repo at repoPath and starts an IPFS node.
func createNode(ctx context.Context, repoPath string) (*core.IpfsNode, error) {
// Open the repo
repo, err := fsrepo.Open(repoPath)
if err != nil {
return nil, err
}

// Construct the node

nodeOptions := &core.BuildCfg{
Online: true,
// For this example, we use NilRouterOption (no routing) since we connect peers directly.
// Bitswap works with directly connected peers without needing DHT lookups.
// In production, you would typically use:
// Routing: libp2p.DHTOption, // Full DHT node (stores and fetches records)
// Routing: libp2p.DHTClientOption, // DHT client (only fetches records)
// No routing: peers are connected directly by address.
// In production use libp2p.DHTClientOption or libp2p.DHTOption
// so the node can find content and peers on the wider network.
Routing: libp2p.NilRouterOption,
Repo: repo,
}
Expand All @@ -134,7 +125,7 @@ func createNode(ctx context.Context, repoPath string) (*core.IpfsNode, error) {

var loadPluginsOnce sync.Once

// Spawns a node to be used just for this run (i.e. creates a tmp repo).
// spawnEphemeral creates a temporary repo, starts a node, and returns its API.
func spawnEphemeral(ctx context.Context) (icore.CoreAPI, *core.IpfsNode, error) {
var onceErr error
loadPluginsOnce.Do(func() {
Expand All @@ -144,7 +135,6 @@ func spawnEphemeral(ctx context.Context) (icore.CoreAPI, *core.IpfsNode, error)
return nil, nil, onceErr
}

// Create a Temporary Repo
repoPath, err := createTempRepo()
if err != nil {
return nil, nil, fmt.Errorf("failed to create temp repo: %s", err)
Expand Down Expand Up @@ -222,21 +212,12 @@ func main() {
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute)
defer cancel()

// Spawn a local peer using a temporary path, for testing purposes
// Spawn a local peer using a temporary path, for testing purposes.
ipfsA, nodeA, err := spawnEphemeral(ctx)
if err != nil {
panic(fmt.Errorf("failed to spawn peer node: %s", err))
}

peerCidFile, err := ipfsA.Unixfs().Add(ctx,
files.NewBytesFile([]byte("hello from ipfs 101 in Kubo")))
if err != nil {
panic(fmt.Errorf("could not add File: %s", err))
}

fmt.Printf("Added file to peer with CID %s\n", peerCidFile.String())

// Spawn a node using a temporary path, creating a temporary repo for the run
fmt.Println("Spawning Kubo node on a temporary repo")
ipfsB, _, err := spawnEphemeral(ctx)
if err != nil {
Expand All @@ -245,6 +226,27 @@ func main() {

fmt.Println("IPFS node is running")

// Connect nodeB to nodeA before adding content. This lets the connection
// finish its setup during the Add below, so the fetch in Part IV is fast.
peerAddrs, err := ipfsA.Swarm().LocalAddrs(ctx)
if err != nil {
panic(fmt.Errorf("could not get peer addresses: %s", err))
}
peerMa := peerAddrs[0].String() + "/p2p/" + nodeA.Identity.String()
fmt.Println("Connecting to peer...")
if err := connectToPeers(ctx, ipfsB, []string{peerMa}); err != nil {
panic(fmt.Errorf("failed to connect to peer: %s", err))
}
fmt.Println("Connected to peer")

peerCidFile, err := ipfsA.Unixfs().Add(ctx,
files.NewBytesFile([]byte("hello from ipfs 101 in Kubo")))
if err != nil {
panic(fmt.Errorf("could not add File: %s", err))
}

fmt.Printf("Added file to peer with CID %s\n", peerCidFile.String())

/// --- Part II: Adding a file and a directory to IPFS

fmt.Println("\n-- Adding and getting back files & directories --")
Expand Down Expand Up @@ -313,29 +315,7 @@ func main() {

/// --- Part IV: Getting a file from another IPFS node

fmt.Println("\n-- Connecting to nodeA and fetching content via bitswap --")

// Get nodeA's actual listening address dynamically.
// We configured TCP-only on 127.0.0.1 with random port, so this will be a TCP address.
peerAddrs, err := ipfsA.Swarm().LocalAddrs(ctx)
if err != nil {
panic(fmt.Errorf("could not get peer addresses: %s", err))
}
peerMa := peerAddrs[0].String() + "/p2p/" + nodeA.Identity.String()

bootstrapNodes := []string{
// In production, use real bootstrap peers like:
// "/dnsaddr/bootstrap.libp2p.io/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN",
// For this example, we only connect to nodeA which has our test content.
peerMa,
}

fmt.Println("Connecting to peer...")
err = connectToPeers(ctx, ipfsB, bootstrapNodes)
if err != nil {
panic(fmt.Errorf("failed to connect to peers: %s", err))
}
fmt.Println("Connected to peer")
fmt.Println("\n-- Fetching content from nodeA via bitswap --")

exampleCIDStr := peerCidFile.RootCid().String()

Expand Down
Loading