Milestone 2: persistent blob store, add/materialize/gc

iroh-blobs 0.35 fs store under <store_dir>/blobs. Add imports files or
whole directory trees (as Collections, deterministic order), Materialize
exports them with a real FICLONE reflink attempt and streaming-copy
fallback, Gc is an explicit mark-and-sweep rooted at the pins. Pins,
trusted peers and hash formats persist in meta.json (atomic writes).
Store reads run on a LocalPool because iroh-blobs entry readers are not
Send. Integration tests drive the real daemon binary: directory round
trip byte-identical, gc keeps pinned/drops unpinned, reflink verified on
the repo's own filesystem (XFS), plus a 32-case proptest round trip.

Dependencies: iroh-blobs =0.35.0 (the store itself; pinned per spec),
iroh-io (AsyncSliceReader traits to read store entries), reflink-copy
(FICLONE with copy fallback, per spec), proptest (dev-only, round-trip
property test).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Bendik Lynghaug
2026-07-14 20:47:11 +02:00
co-authored by Claude Fable 5
parent 4e1a95613b
commit e256f73439
12 changed files with 5816 additions and 50 deletions
+4 -3
View File
@@ -82,15 +82,16 @@ fn main() -> Result<()> {
async fn run(config: Config) -> Result<()> {
let socket_path = config.socket_path.clone();
let daemon = Arc::new(daemon::Daemon::open(config).await?);
let listener = server::bind_socket(&socket_path)?;
let daemon = Arc::new(daemon::Daemon::new(config));
let result = tokio::select! {
r = server::serve(listener, daemon) => r,
r = server::serve(listener, daemon.clone()) => r,
r = shutdown_signal() => r.map(|signal| info!(signal, "shutting down")),
};
// Best-effort cleanup so the next start doesn't find a stale socket.
// Flush the store and remove the socket so the next start is clean.
daemon.store().shutdown().await;
let _ = std::fs::remove_file(&socket_path);
result
}