hv Haven

Documentation

Getting started

Everything below applies to 0.9.x. Commands are stable within the 0.9 line; flags marked experimental may change before 1.0.

Install

Release archives contain a single static binary. Verify the checksum, then move it onto your path:

curl -sSLO https://berkas.zanity.net/dl/haven-0.9.4-linux-amd64.tar.gz
curl -sSLO https://berkas.zanity.net/dl/haven-0.9.4-SHA256SUMS
sha256sum -c --ignore-missing haven-0.9.4-SHA256SUMS
tar xzf haven-0.9.4-linux-amd64.tar.gz
install -m 0755 haven /usr/local/bin/haven

Building from source needs Go 1.22 or newer and nothing else:

git clone https://berkas.zanity.net/git/haven.git
cd haven && make build   # writes ./out/haven

Concepts

Three things are worth understanding before using the CLI.

Chunks

Files are split at content-defined boundaries using a rolling hash, with a 64 KiB target chunk size. Inserting a byte near the start of a large file shifts one or two chunk boundaries rather than invalidating everything after it. Each chunk is identified by its BLAKE3 digest.

Store

A store is a directory of chunks shared by many archives. Pass --store to reuse it across builds; that is where deduplication happens. Without a store, each archive is self-contained and dedupes only against itself.

Manifest

Each archive begins with a manifest listing paths, modes, sizes and the chunk digests that make up each file. The manifest itself is hashed, so a single digest pins the entire tree.

Commands

create

haven create [--store DIR] [-o FILE] [--exclude GLOB]... PATH

Packs PATH into an archive. Repeat --exclude as needed; patterns are matched against paths relative to PATH. With -o - the archive is written to stdout.

extract

haven extract [--into DIR] [--strip N] FILE

Restores an archive. Refuses to overwrite existing files unless --force is given. Paths escaping the destination directory are rejected outright.

verify

haven verify [--deep] FILE

Checks the manifest digest and every chunk digest. --deep also re-chunks file contents to confirm the boundaries match what the manifest claims. Slower, but catches a corrupt store.

list

haven list [--long] [--json] FILE

Prints archive contents without extracting. --json emits one object per line for piping into jq.

gc

haven gc --store DIR --keep FILE...

Deletes chunks in the store that are unreachable from the archives listed with --keep. Always dry-runs first; add --commit to actually remove. Losing a chunk that some other archive needs makes that archive unextractable, so be explicit about what you keep.

inspect

haven inspect [--chunks] FILE

Dumps header fields, format version and manifest statistics. Mostly useful when filing a bug report.

Exit codes

  • 0 — success
  • 1 — usage error
  • 2 — I/O error
  • 3 — integrity failure (digest mismatch)
  • 4 — unsupported format version

Using it in CI

Keep the store on a cache volume that survives between runs, and key the archive name on the commit:

haven create --store "$CACHE_DIR/hv-store" \
              --exclude '*.map' --exclude 'node_modules/**' \
              -o "artifacts/$CI_COMMIT_SHORT_SHA.hv" ./dist

haven gc --store "$CACHE_DIR/hv-store" \
          --keep artifacts/*.hv --commit

Run gc after pruning old artifacts, not before — the --keep list is the source of truth for what stays reachable.

Limitations

  • No encryption. Pipe through age or similar if you need it.
  • No compression of individual chunks yet; planned for 0.10.
  • Hard links are stored as separate files. Symlinks are preserved as symlinks.
  • Extended attributes and ACLs are not captured.
  • Windows is not supported and is not on the roadmap.