| 1 | package badgerds |
| 2 | |
| 3 | import ( |
| 4 | "fmt" |
| 5 | "os" |
| 6 | "path/filepath" |
| 7 | |
| 8 | logging "github.com/ipfs/go-log/v2" |
| 9 | "github.com/ipfs/kubo/plugin" |
| 10 | "github.com/ipfs/kubo/repo" |
| 11 | "github.com/ipfs/kubo/repo/fsrepo" |
| 12 | |
| 13 | humanize "github.com/dustin/go-humanize" |
| 14 | badgerds "github.com/ipfs/go-ds-badger" |
| 15 | ) |
| 16 | |
| 17 | var log = logging.Logger("plugin/badgerds") |
| 18 | |
| 19 | // Plugins is exported list of plugins that will be loaded. |
| 20 | var Plugins = []plugin.Plugin{ |
| 21 | &badgerdsPlugin{}, |
| 22 | } |
| 23 | |
| 24 | type badgerdsPlugin struct{} |
| 25 | |
| 26 | var _ plugin.PluginDatastore = (*badgerdsPlugin)(nil) |
| 27 | |
| 28 | func (*badgerdsPlugin) Name() string { |
| 29 | return "ds-badgerds" |
| 30 | } |
| 31 | |
| 32 | func (*badgerdsPlugin) Version() string { |
| 33 | return "0.1.0" |
| 34 | } |
| 35 | |
| 36 | func (*badgerdsPlugin) Init(_ *plugin.Environment) error { |
| 37 | return nil |
| 38 | } |
| 39 | |
| 40 | func (*badgerdsPlugin) DatastoreTypeName() string { |
| 41 | return "badgerds" |
| 42 | } |
| 43 | |
| 44 | type datastoreConfig struct { |
| 45 | path string |
| 46 | syncWrites bool |
| 47 | truncate bool |
| 48 | |
| 49 | vlogFileSize int64 |
| 50 | } |
| 51 | |
| 52 | // BadgerdsDatastoreConfig returns a configuration stub for a badger datastore |
| 53 | // from the given parameters. |
| 54 | func (*badgerdsPlugin) DatastoreConfigParser() fsrepo.ConfigFromMap { |
| 55 | return func(params map[string]any) (fsrepo.DatastoreConfig, error) { |
| 56 | var c datastoreConfig |
| 57 | var ok bool |
| 58 | |
| 59 | c.path, ok = params["path"].(string) |
| 60 | if !ok { |
| 61 | return nil, fmt.Errorf("'path' field is missing or not string") |
| 62 | } |
| 63 | |
| 64 | sw, ok := params["syncWrites"] |
| 65 | if !ok { |
| 66 | c.syncWrites = false |
| 67 | } else { |
| 68 | if swb, ok := sw.(bool); ok { |
| 69 | c.syncWrites = swb |
| 70 | } else { |
| 71 | return nil, fmt.Errorf("'syncWrites' field was not a boolean") |
| 72 | } |
| 73 | } |
| 74 | |
| 75 | truncate, ok := params["truncate"] |
| 76 | if !ok { |
| 77 | c.truncate = true |
| 78 | } else { |
| 79 | if truncate, ok := truncate.(bool); ok { |
| 80 | c.truncate = truncate |
| 81 | } else { |
| 82 | return nil, fmt.Errorf("'truncate' field was not a boolean") |
| 83 | } |
| 84 | } |
| 85 | |
| 86 | vls, ok := params["vlogFileSize"] |
| 87 | if !ok { |
| 88 | // default to 1GiB |
| 89 | c.vlogFileSize = badgerds.DefaultOptions.ValueLogFileSize |
| 90 | } else { |
| 91 | if vlogSize, ok := vls.(string); ok { |
| 92 | s, err := humanize.ParseBytes(vlogSize) |
| 93 | if err != nil { |
| 94 | return nil, err |
| 95 | } |
| 96 | c.vlogFileSize = int64(s) |
| 97 | } else { |
| 98 | return nil, fmt.Errorf("'vlogFileSize' field was not a string") |
| 99 | } |
| 100 | } |
| 101 | |
| 102 | return &c, nil |
| 103 | } |
| 104 | } |
| 105 | |
| 106 | func (c *datastoreConfig) DiskSpec() fsrepo.DiskSpec { |
| 107 | return map[string]any{ |
| 108 | "type": "badgerds", |
| 109 | "path": c.path, |
| 110 | } |
| 111 | } |
| 112 | |
| 113 | func (c *datastoreConfig) Create(path string) (repo.Datastore, error) { |
| 114 | log.Error("badger v1 datastore is deprecated and will be removed later in 2026, migrate to flatfs or experimental pebbleds: https://github.com/ipfs/kubo/issues/11186") |
| 115 | fmt.Fprintf(os.Stderr, ` |
| 116 | ╔════════════════════════════════════════════════════════════════════════════╗ |
| 117 | ║ ║ |
| 118 | ║ ERROR: BADGER v1 DATASTORE IS DEPRECATED ║ |
| 119 | ║ ║ |
| 120 | ║ This datastore is based on badger 1.x which has not been maintained ║ |
| 121 | ║ by its upstream maintainers for years and has known bugs (startup ║ |
| 122 | ║ timeouts, shutdown hangs, file descriptor exhaustion, and more). ║ |
| 123 | ║ ║ |
| 124 | ║ Badger v1 support will be REMOVED later in 2026. ║ |
| 125 | ║ ║ |
| 126 | ║ To migrate: ║ |
| 127 | ║ 1. Create a new IPFS_PATH with flatfs (or experimental pebbleds ║ |
| 128 | ║ if flatfs does not serve your use case): ║ |
| 129 | ║ export IPFS_PATH=/path/to/new/repo ║ |
| 130 | ║ ipfs init --profile=flatfs ║ |
| 131 | ║ 2. Move pinned data via ipfs dag export/import ║ |
| 132 | ║ or ipfs pin ls -t recursive|add ║ |
| 133 | ║ 3. Decommission the old badger-based node ║ |
| 134 | ║ ║ |
| 135 | ║ See https://github.com/ipfs/kubo/blob/master/docs/datastores.md ║ |
| 136 | ║ https://github.com/ipfs/kubo/issues/11186 ║ |
| 137 | ║ ║ |
| 138 | ╚════════════════════════════════════════════════════════════════════════════╝ |
| 139 | `) |
| 140 | p := c.path |
| 141 | if !filepath.IsAbs(p) { |
| 142 | p = filepath.Join(path, p) |
| 143 | } |
| 144 | |
| 145 | err := os.MkdirAll(p, 0o755) |
| 146 | if err != nil { |
| 147 | return nil, err |
| 148 | } |
| 149 | |
| 150 | defopts := badgerds.DefaultOptions |
| 151 | defopts.SyncWrites = c.syncWrites |
| 152 | defopts.Truncate = c.truncate |
| 153 | defopts.ValueLogFileSize = c.vlogFileSize |
| 154 | |
| 155 | return badgerds.NewDatastore(p, &defopts) |
| 156 | } |