Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Etherdrive

Etherdrive is a local-first organization layer on top of etherchunk. It groups uploaded files into collections: named sets you can list, extend and prune, without ever building a Swarm manifest of its own.

Etherchunk sees only individual files. Every file etherdrive uploads is a separate etherchunk upload <file> call with its own root hash, its own registry row, and its own reclaimable slots. Collections exist purely in etherdrive's SQLite database, so grouping and listing cost nothing on the network: there is nothing to re-upload and no manifest to rebuild.

Installation

npm install --global etherdrive

Etherdrive shells out to the etherchunk binary, so install that too and make sure it is on your PATH:

npm install --global etherchunk

Configuration

Etherdrive does not define its own upload configuration. It inherits etherchunk's environment, which must be set up as described in etherchunk's Postage Batch Setup:

export ETHERCHUNK_UPLOAD_URL="http://localhost:1633/chunks"
export ETHERCHUNK_SIGNER="<private key hex>"
export ETHERCHUNK_BATCH_ID="<batch id hex>"
export ETHERCHUNK_BATCH_DEPTH=<depth of your batch>

Etherdrive adds three of its own, all optional:

export ETHERDRIVE_ETHERCHUNK_BIN="etherchunk"           # path to the etherchunk binary
export ETHERDRIVE_GATEWAY_URL="http://localhost:1633"   # base URL used when printing browse links
export ETHERDRIVE_STATE_DIR="$HOME/.etherdrive"         # where etherdrive.db lives

If ETHERDRIVE_GATEWAY_URL is unset, it is derived from ETHERCHUNK_UPLOAD_URL by stripping the trailing /chunks, falling back to http://localhost:1633.

Usage

# Upload files and put them in a collection (created on first use)
etherdrive add photos ~/pics/a.jpg ~/pics/b.jpg

# Give an entry a label other than its basename
etherdrive add docs ~/work/2024-final-v3.pdf --as spec.pdf

# Pass upload options straight through to etherchunk
etherdrive add docs ~/work/spec.pdf --encrypt --redundancy=2 --parallelism=64

# List the entries in a collection
etherdrive ls photos

# List every collection
etherdrive ls

# Remove an entry, reclaiming its slots
etherdrive rm photos b.jpg

# Delete a collection and everything in it
etherdrive drop photos

add

etherdrive add <collection> <file...> [--as <name>] [--encrypt] [--redundancy=<0-4>] [--parallelism=<n>]

For each path, etherdrive runs etherchunk upload <file> with the given flags, reads the root hash from stdout, and records an entry in the named collection. The collection is created if it does not exist.

add always uploads. There is no deduplication of any kind: no content hashing, no path matching. Adding the same path twice produces two entries with two root hashes and two sets of slots, and adding a file to a second collection uploads it a second time.

If etherchunk exits non-zero for a file, nothing is recorded for that file. Remaining files in the same invocation are still attempted, and etherdrive exits non-zero at the end.

--as applies only when a single file is given; the default label is the file's basename. Labels are etherdrive's own bookkeeping — they are not written to Swarm and need not be unique.

ls

etherdrive ls [collection]

With no argument, prints each collection with its entry count and total size. With a collection name, prints one line per entry:

3f9a1c04...  a.jpg    4.2 MB  uploaded=2026-07-29T10:14:03Z  http://localhost:1633/bzz/3f9a1c04…9e2b/
b2c18475...  b.jpg   11.8 MB  uploaded=2026-07-29T10:14:19Z  http://localhost:1633/bzz/b2c18475…c7d1/

The first column is abbreviated to eight characters for reading; the URL carries the full reference. Each entry is browsable on its own, because etherchunk wraps every single-file upload in a one-entry Mantaray manifest with a website-index-document pointer.

rm

etherdrive rm <collection> <name|root-hash>

Runs etherchunk delete <root-hash> to reclaim the entry's slots, then deletes the entry row. Removal always purges. An entry belongs to exactly one collection, so there is no case where the data should outlive its membership.

An entry is addressed by its label or by any prefix of its root hash, so the abbreviated hash ls prints is enough. If several entries in the collection match:

  • When the matches share a root hash, they hold identical bytes and occupy the same amount of batch space, so one is removed and the rest are left in place with a note. A root hash could not have told them apart.
  • When the root hashes differ, etherdrive lists the matches and does nothing; disambiguate with a root hash.

If etherchunk has no record of the hash, etherdrive warns that there were no slots to reclaim and still drops the local entry. Any other etherchunk failure leaves the entry in place.

drop

etherdrive drop <collection> [--yes]

Deletes the collection along with every entry in it, calling etherchunk delete for each. Because this can reclaim a large amount of data at once, etherdrive asks for confirmation when stdin is a TTY, and requires --yes when it is not.

$ etherdrive drop photos
Delete collection 'photos' — 12 entries, 148 MB? [y/N]

If any entry cannot be deleted, that entry and the collection are kept so a retry can finish the job.

Design

Etherdrive holds no chunk-level state. It does not stamp or split chunks and it never talks to Bee. The slot map and the per-file chunk lists stay entirely in etherchunk's hands, so the two tools cannot disagree about which slots are occupied; etherdrive's only network effect is invoking etherchunk upload and etherchunk delete.

~/.etherdrive/etherdrive.db

A single SQLite database. Unlike etherchunk's state, it is not partitioned per postage batch: collections may span batches, so the batch each entry was uploaded under is recorded on the entry itself and passed along when deleting.

CREATE TABLE collections (
    id          INTEGER PRIMARY KEY AUTOINCREMENT,
    name        TEXT NOT NULL UNIQUE,
    created_at  INTEGER NOT NULL          -- unix ms
);

CREATE TABLE entries (
    id               INTEGER PRIMARY KEY AUTOINCREMENT,
    collection_id    INTEGER NOT NULL REFERENCES collections(id) ON DELETE CASCADE,
    root_hash        BLOB NOT NULL,       -- 32 bytes, or 64 when encrypted (ref || key)
    source_path      TEXT NOT NULL,       -- absolute path at upload time
    name             TEXT NOT NULL,       -- label within the collection; defaults to basename
    size             INTEGER NOT NULL,    -- bytes on disk at upload time
    batch_id         BLOB NOT NULL,       -- 32 bytes; the batch holding these chunks
    batch_depth      INTEGER NOT NULL,    -- that batch's depth at upload time
    encrypted        INTEGER NOT NULL DEFAULT 0,
    redundancy_level INTEGER NOT NULL DEFAULT 0,
    uploaded_at      INTEGER NOT NULL     -- unix ms
);
CREATE INDEX idx_entries_collection ON entries(collection_id);
CREATE INDEX idx_entries_root_hash ON entries(root_hash);

An entry belongs to exactly one collection. Neither root_hash nor (collection_id, name) is unique: since add always uploads, the same bytes can legitimately appear as several entries under different root hashes, in the same collection or in different ones.

Invoking etherchunk

Etherchunk writes progress to stderr and the resulting root hash, as bare hex, to stdout. Etherdrive spawns it with stderr inherited, so upload progress appears live, and captures stdout, taking the last non-empty line and requiring it to match /^[0-9a-f]{64}([0-9a-f]{64})?$/ (64 hex characters, or 128 for an encrypted upload). Anything else is treated as a failed upload and nothing is recorded.

Deletion is etherchunk delete <root-hash-hex>, which returns the entry's slots to the batch's free bitmap. Because etherchunk registers each single-file upload as one row covering all of that file's chunks, a delete is exactly as granular as an etherdrive entry.

Both commands run with ETHERCHUNK_BATCH_ID and ETHERCHUNK_BATCH_DEPTH pinned to the batch the entry belongs to, so entries uploaded under a batch you are no longer using still get deleted from the right slot map. Pinning is reliable even when you keep configuration in a .env file: etherchunk calls loadEnvFile(), which does not overwrite variables already present in the environment it inherits. The one value etherdrive prefers from the current environment is the depth of the batch you are using now, since a dilution plus etherchunk migrate makes the stored depth stale.

Why no manifests

A collection deliberately does not resolve to a Swarm address. Making it one would mean building a Mantaray trie over the collection's members, and since etherchunk exposes no way to assemble a trie from already-uploaded refs, every member would have to be rebuilt and re-uploaded on every change. Keeping collections local means add, rm and drop are all local writes plus at most one etherchunk call per file, and the price paid to the postage batch is one upload per add.

The trade-off is that a collection cannot be shared as a single link. Each of its entries has its own /bzz/<root>/ URL, and that is the unit of sharing.

Development

pnpm install
pnpm build

License

MIT

About

CLI for Ethereum Swarm that groups etherchunk uploads into local collections, so files can be listed, labelled and pruned without rebuilding a manifest.

Topics

Resources

Stars

Watchers

Forks

Contributors

Languages