Public paid-storage and free/open-shelter discovery is explicitly opt-in via
--service-profile. It exposes a bound /services/v1 snapshot and can publish
coarse availability and prices as kind 31402 using an external dedicated signer.
See public service discovery for the contract,
private profile, direct/Tor relay routing, acknowledgement and withdrawal limits.
Private owner/friend nodes remain unpublished by default.
Run your own Blossom storage with Tor when you need easy private reachability, or use your own HTTPS setup when you do not.
Wildbloom Node is a small, cross-platform Blossom server. It stores blobs on your disk by SHA-256 and accepts only tightly scoped Nostr authorisation. Tor is an optional transport, not a storage requirement. The same node can use a persistent Tor v3 onion, stay local-only, or sit behind an operator-managed HTTPS reverse proxy. It runs no WebRTC, STUN or TURN itself.
This is a production candidate, not a durability promise. The headless node targets macOS, Linux and Windows. The native tray application lets the operator choose Tor or direct mode. It bundles a signature-verified Tor Expert Bundle for the Tor choice, but does not start it in direct mode. Preview installers are distributed separately from production releases. The Mac preview has verified Developer ID signatures and Apple notarisation; Windows signing, automatic updates and clean-machine acceptance remain open.
A fresh desktop install waits for the operator to choose a transport before it starts either process. Existing saved settings continue to select Tor unless the operator changes them.
The 0.3.0 desktop also manages owner-directed storage pools: import a private signed receipt, check part health without signing, and explicitly start or stop bounded automatic repair. Run this owner service on a machine allowed to reconstruct ciphertext. See Desktop pools and the physical recovery checklist.
Blossom gives Nostr applications a sensible content-addressed storage protocol, but using somebody else's server is still using somebody else's disk. They can lose it, remove it or stop paying the bill. Wildbloom Node makes a user's own machine a standard Blossom server and lets another authorised node copy the blob through BUD-04.
It is not a torrent client. Every replica currently stores a complete blob and serves it over ordinary Blossom HTTP. Nostr can carry private storage offers and replication intent; Tor or direct HTTPS carries the bytes. A separate QUIC and opaque-relay lane is a future spike, not part of the durability claim. RelaySwarm's WebRTC session remains for live browser media rather than storage.
- BUD-01
GETandHEAD, optional file extensions and single byte ranges. Reads need no authorisation: anyone who knows a blob's hash can fetch it, and an unknown hash returns 404. Only writes, listing and deletion need a signed kind24242event. - BUD-02 streaming
PUT /uploadwithContent-LengthandX-SHA-256. - BUD-04
PUT /mirrorfrom hash-addressed onion origins through the private Tor SOCKS listener, or from public HTTPS origins through direct mode. - BUD-06 authorised upload preflight with quota and per-blob checks.
- Strict BUD-11 kind
24242signature, operation, hash, server and expiry checks. - Authenticated, self-only BUD-12 listing with bounded cursor pagination, plus claim-aware deletion. One signer cannot list or delete another signer's claims.
- Persistent SQLite metadata and a disk content-addressed store.
- Complete encrypted replicas or erasure-coded parts stored as ordinary blobs, with signed pool receipt inspection and owner-side reconstruction/repair. The desktop shows timestamped per-part observations and supervises one pool process at a time; receipts persist, but repair authority needs explicit approval after app restart.
- Pre-stream global quota reservation, per-blob limits, deduplication and interrupted-upload cleanup.
- Deny-by-default writes, bounded concurrent streams, complete integrity scans and repair from previously verified mirror sources.
- An optional headless replica coordinator with an owner-signed local policy,
complete remote byte verification, scoped external authorisation and bounded
BUD-04 repairs across explicitly configured targets. It can maintain a
whole directory of policies from one process, and enrol a changing set of
blobs from an operator-supplied inventory into intake and chunked archive
policies. Start it with
wildbloomd replicas run --policy <signed-policy.json> --state-dir <DIR> --owner <HEX>(add--oncefor a single pass), orwildbloomd replicas run --policy-dir <DIR> --state-root <DIR> --owner <HEX>for a directory of policies. The owner-side pool process is a separate command,wildbloomd replicas pool-repair; see the replica maintenance guide and pool storage for the full arguments. - Claim-aware owner, friend and guest retention over one deduplicated CAS. Friends have expiring per-key byte ceilings; signed guest mirrors use only spare capacity and are evicted first.
- Opaque attachment serving for friend-only and guest-only blobs, including
nosniff; conflicting owner MIME claims also fail opaque. - Crash reconciliation restores indexed tombstones and removes unindexed files left by interrupted file/database transactions.
- Loopback-only binding by default, with a persistent Tor v3 onion or an operator-supplied HTTPS origin.
- Native Rust and Tauri compile CI configured for macOS, Linux and Windows.
- A native Tauri tray shell with platform data directories, start-at-login, storage status and no private-key input.
- Installed
.deband NSIS preview acceptance on fresh hosted Linux and Windows runners, including Tor bootstrap, Blossom health, single-instance behaviour, child cleanup and uninstall.
Opt-in direct operator checkout and private full-read storage audits are available in this source. The browser discovers recommendations from trusted signed server lists; it does not automatically choose strangers to hold data. The optional coordinator reports recently verified configured failure groups; operator declarations do not prove physical independence or future retention. Local repair can restore a damaged copy only when the node already recorded a verified source which is still online. Those boundaries are documented in the roadmap.
The storage priority contract defines the exact admission and eviction promises. The implementation and adversarial local tests are present; cross-platform CI and the independent V4V recovery journey remain release evidence rather than assumptions.
The desktop application is the intended consumer install. Try the signed Mac preview or Windows/Linux previews, or build from a revision you have reviewed. The releases include checksums and exact validation scope. Windows packages remain unsigned; Mac packages are manual installs with signing evidence and remaining gates.
Install Rust 1.94.1, the platform's normal Tauri 2 build prerequisites, GnuPG and the Tauri CLI. Choose the matching target and Tor bundle name:
| System | Rust target | Tor bundle name |
|---|---|---|
| Windows x64 | x86_64-pc-windows-msvc |
windows-x86_64 |
| Linux x64 | x86_64-unknown-linux-gnu |
linux-x86_64 |
| macOS Apple Silicon | aarch64-apple-darwin |
macos-aarch64 |
| macOS Intel | x86_64-apple-darwin |
macos-x86_64 |
From a fresh clone, replace the two values below with that row:
cargo install tauri-cli --version 2.11.4 --locked
scripts/prepare-tor-runtime.sh <tor-bundle-name> desktop/src-tauri/tor-runtime
cargo build --locked --release --target <rust-target> -p wildbloomd
cp target/<rust-target>/release/wildbloomd desktop/src-tauri/binaries/wildbloomd-<rust-target>
# On macOS only: scripts/sign-macos-tor-runtime.sh - desktop/src-tauri/tor-runtime
cd desktop/src-tauri
cargo tauri build --target <rust-target>On Windows, both daemon paths end in .exe. This produces an explicitly
unsigned preview. The verified Tor runtime is bundled as a complete resource,
not fetched on first start. Production signing and updater artefacts are made
only by the fail-closed release workflow described in the release process.
Linux desktop releases use native .deb and .rpm packages; the headless
daemon can be built as an ordinary binary on other Linux distributions. Both
packages have detached release signatures; Linux updates are explicit package
installs rather than an AppImage replacement dressed up as an auto-update.
The headless path needs Rust 1.94.1
or later. Tor mode additionally needs a current
Tor Expert Bundle, with the tor
executable on PATH.
git clone https://github.com/forgesworn/wildbloom-node.git
cd wildbloom-node
cargo build --release
./target/release/wildbloomd --allow-pubkey <64-lower-case-hex-public-key>The first Tor bootstrap can take several minutes and has a fifteen-minute
allowance; later starts reuse its private directory cache. Once ready, the log
prints the stable .onion Blossom URL. Add that URL to a Blossom-capable client. The
node stores data in the operating system's per-user application-data directory
unless --data-dir or WILDBLOOM_DATA_DIR says otherwise. Without an owner,
friend grant, activated paid allowance or explicit open-shelter policy it remains read-only.
For a supported local-only node without Tor, keep the same explicit owner authority:
cargo run -p wildbloomd -- --no-tor --allow-pubkey <64-lower-case-hex-public-key>For an internet-facing direct node, keep wildbloomd on loopback, terminate
TLS in a hardened reverse proxy on the same host and give the daemon that exact
public origin. Direct public-HTTPS mirroring and repair must be selected
explicitly:
cargo run -p wildbloomd -- \
--no-tor \
--public-url https://blossom.example \
--direct-https-mirrors \
--allow-pubkey <64-lower-case-hex-public-key>Direct mode reveals ordinary client and server network metadata. It does not configure DNS, TLS, a firewall, NAT traversal or a router. Plain public HTTP origins are refused; only loopback and onion origins may use HTTP.
Useful controls:
--quota-bytes <BYTES> total stored blob quota, default 10 GiB
--max-blob-bytes <BYTES> maximum single blob, default 1 GiB
--data-dir <PATH> persistent data directory, default per-user app data
--public-url <URL> public origin placed in blob descriptors; needs --no-tor
--server-name <HOST> exact BUD-11 server tag value, repeatable or comma-separated
--allow-pubkey <HEX> owner public key, repeatable
--friend-grant <P:L:E> PUBKEY:BYTE_LIMIT:EXPIRES_AT, Unix expiry, repeatable
--open-shelter admit unknown signed mirrors as guest data
--device-grants accept owner-signed device grants on /grants/v1
--group-mode closed group storage node (docs/GROUP-GRANTS.md)
--group-keeper <K:C[:R+R]> KEEPER:CEILING_BYTES[:ROOM+ROOM] to enrol, repeatable
--pairing-issuer issue one-use Foregather live grants to devices
--pairing-relay-url <URL> Foregather API base for --pairing-issuer
--pairing-operator-token-file <PATH> private file holding its operator token
--turn-issuer issue short-lived TURN credentials to devices
--turn-url <URL> stun:, turn: or turns: URL for --turn-issuer, 1 to 4
--turn-secret-file <PATH> private file holding the TURN static-auth-secret
--service-profile <PATH> opt-in public service listing (docs/SERVICE-DISCOVERY.md)
--checkout-profile <PATH> opt-in paid checkout, needs --no-tor (docs/CHECKOUT.md)
--max-concurrent-writes <N> concurrent upload/mirror limit, default 4
--repair-interval <SECONDS> integrity and repair interval, default 3600
--verify-storage verify every stored byte and exit
--bind <IP:PORT> local listener, default 127.0.0.1:3742
--tor-bin <PATH> Tor executable, default tor
--no-tor do not start managed Tor
--direct-https-mirrors fetch public HTTPS mirrors directly
Each flag also has a WILDBLOOM_* environment variable, listed by
wildbloomd --help, for example WILDBLOOM_DATA_DIR for --data-dir.
--public-url is refused in Tor mode, where the onion address is the public
origin. With --no-tor and no --public-url, the accepted server name is
localhost.
Do not put the local listener on the public internet merely because the flag exists. If a conventional HTTPS reverse proxy is genuinely required, read the threat model first.
Applications use the selected onion, HTTPS or local URL exactly as they use another Blossom server:
- Hash the exact bytes with SHA-256.
- Sign a short-lived kind
24242uploadevent for that hash and this node's exact hostname. PUT /uploadwithContent-Length,X-SHA-256and the event inAuthorization: Nostr <base64-event>. BUD-01 specifies standard base64; the node also accepts URL-safe and unpadded forms.- Keep the returned descriptor URL or publish it through the application's normal Nostr records.
- To add another replica, sign for the destination node and call its
PUT /mirrorwith the source descriptor URL.
Any NIP-01 signer can produce the event; the node never sees a private key and
the repository ships no application signer. The node accepts only this shape:
kind 24242; non-empty content of at most 1024 bytes; exactly one t tag set
to upload; an x tag holding the lower-case hex hash; a server tag naming
the node's host; and an expiration tag no more than five minutes after
created_at. A created_at more than 30 seconds in the future is refused.
The server tag is the .onion hostname in Tor mode, the --public-url host
in direct mode, or localhost for a plain --no-tor node, even when curl
targets 127.0.0.1. --server-name overrides it. Sign this unsigned template
with your own signer and keep the signed JSON in $EVENT_JSON:
{"kind":24242,"created_at":<now>,"content":"Upload file.bin","tags":[["t","upload"],["x","<sha256>"],["server","localhost"],["expiration","<now+300>"]]}HASH=$(shasum -a 256 file.bin | cut -d' ' -f1)
AUTH=$(printf '%s' "$EVENT_JSON" | base64 | tr '+/' '-_' | tr -d '=\n')
curl -X PUT http://127.0.0.1:3742/upload \
-H "Authorization: Nostr $AUTH" \
-H "X-SHA-256: $HASH" \
-H 'Content-Type: application/octet-stream' \
--data-binary @file.bin
curl -I http://127.0.0.1:3742/$HASHA new blob returns 201 and a JSON descriptor with url, sha256, size,
type, uploaded and class; a blob the node already holds returns 200. Any
authorisation failure, including a key the node does not recognise, returns
401. The read-back needs no authorisation and returns 200 with ETag and
Content-Length headers.
An operator may add an invited key with
--friend-grant <pubkey>:<byte-limit>:<expires-at>. --open-shelter is a
separate, explicit policy: unknown keys may submit signed BUD-04 mirrors when
the high free-space watermark remains intact, but they still cannot upload
directly and receive no retention promise.
--group-mode runs a closed node for the devices of enrolled groups. Each
--group-keeper <keeper-hex>:<ceiling-bytes> names a room authority key the
node serves and the most one of its groups may hold. The keeper signs kind
27541 grants that the node takes on POST /groups/v1/events; only granted
keys may use the node, every read needs a BUD-11 get token, and a read shows
only what the signer's own group holds. It refuses --open-shelter,
--service-profile and --checkout-profile, so the node is never listed or
open to strangers. See docs/GROUP-GRANTS.md.
Wildbloom's browser app keeps encryption client-side, so a node stores ciphertext rather than plaintext. See the exact protocol and trust boundaries and the V4V dogfood path.
cargo fmt --check
cargo clippy --workspace --all-targets -- -D warnings
cargo test --workspace
scripts/audit-dependencies.shSecurity issues should follow SECURITY.md. Contributions are welcome under the MIT licence. Desktop packages also retain the exact upstream licences for their signature-verified Tor runtime; see third-party notices.
If you run a Wildbloom Node, you are its operator under UK law, with duties
under the Online Safety Act 2023 and UK GDPR when it accepts content from
anyone other than yourself. Read
docs/OPERATOR-RESPONSIBILITIES.md
before enabling a friend grant or open shelter. It covers what the software
can and cannot do today for removing a blob by hash and blocking an
uploader, and what you need to publish for people to report content to you.
docs/legal/report-handling.md is the
report-handling runbook for a ForgeSworn-run node specifically. None of
these documents are legal advice.
If this is useful, sponsor TheCryptoDonkey on GitHub, support BRAYs on Ko-fi, or back ForgeSworn on Geyser.
The daemon pins Shelter Kit v0.5.0, which adds schema-6 protected capacity
allowances and durable sale holds. An explicit --checkout-profile enables
Lightning and LNURLcash checkout with authoritative receiving checks. It is off
by default and requires operator-owned credentials; see setup and limitations. See the paid core contract.
Back up the data directory before upgrading. Schema 6 cannot be reopened with the previous schema-5 core; do not downgrade by changing the schema version. Published desktop installers are separate release artefacts and do not change when this source dependency pin changes.