brb bin-packs a directory tree into disc-sized groups,
builds one self-contained SquashFS image per disc, encrypts it with
age,
computes par2 recovery data over the ciphertext, and images each disc to an ISO ready to
burn to BD-R, BD-R DL or BDXL.
The last step of a restore is
mount -o loop,ro disc07.squashfs /mnt
which needs nothing but the Linux kernel — no brb, no Python,
no bespoke archive format.
$ brb plan . config: /home/jz/.config/brb/config==> scanning /home/jz ok 431006 entries, 198.42 GiB of file data before compression==> planning disc layout (dry run, nothing is built) . raw content budget per disc: 20.49 GiB (image budget 20.49 GiB / ratio 1.000) . disc 01: 118204 files, 20.49 GiB raw . disc 02: 47455 files, 20.48 GiB raw . disc 03: 91620 files, 20.49 GiB raw . disc 04: 62933 files, 20.47 GiB raw . disc 05: 38017 files, 20.49 GiB raw . disc 06: 21744 files, 20.48 GiB raw . disc 07: 15108 files, 20.49 GiB raw . disc 08: 9622 files, 20.46 GiB raw . disc 09: 5904 files, 20.49 GiB raw . disc 10: 2232 files, 14.08 GiB raw ok 10 disc(s) at pack ratio 1.000, 198.42 GiB of raw content$ brb backup --verify-roundtripwarn staging holds UNENCRYPTED squashfs images while the backup runs . each image is deleted as soon as it is encrypted and verifiedContinue? [y/N] y==> building images (image budget 20.49 GiB per disc) . disc 1: packing 118204 files, 20.49 GiB raw ok disc 1 image: 17.24 GiB (compressed to 0.841 of raw) . encrypting and hashing disc01.squashfs . encrypt disc01.squashfs[========================] 100.0% 17.24 GiB / 17.24 GiB . generating 10% recovery data (17652 blocks of 1.00 MiB, 1765 recovery blocks) . verifying disc 1 decrypts back to the same bytes ok disc 1 round-trip verified ok disc01.squashfs encrypted, protected, plaintext removed ok backup complete in 214 minute(s)
01 — the whole idea
Every disc is independent.
Losing disc 7 loses exactly the files on disc 7. Every other disc still restores on its own.
There is no parity spread across the set, no volume 1 of 20 that the rest depend on, no
catalogue you have to find first. Each disc is a complete, mountable filesystem.
Ten discs, 412,839 files, 198.42 GiB. Each one restores on its own.
set10 of 10 discs restore on their own
files412,839 of 412,839 still restore
data198.42 GiB of 198.42 GiB still restore
Nothing is damaged. Any one of these discs restores its own files without the other nine.
A scratch is not a loss
par2 recovery data is computed over the encrypted bytes, at 10% by default, so it
protects exactly what is physically on the disc. A disc that no longer reads cleanly is
copied off, repaired from the par2 volumes stored beside it, then decrypted as normal.
Nothing is written back to the disc — a BD-R is written once.
A loss is bounded
When par2 cannot repair an image, brb refuses to decrypt it rather than hand you plausible
garbage. You lose that disc's files and nothing else — and the encrypted index on every
other disc still tells you exactly which files those were.
Every disc carries the shape
Each disc carries the full directory skeleton of the original tree — directories, symlinks,
device nodes. Mount any single disc and you see the whole shape of the backup, with that
disc's files present and the rest absent.
02 — how a disc is made
One direction, and nothing invented at the end of it.
The bin-packer is brb's own. Everything it hands off to is a format that was specified,
documented and widely implemented long before brb, and that has every chance of outlasting it.
1bin-packthe tree into disc-sized groups
Files are sorted largest-first and packed greedily into one open disc at a time,
backfilling smaller files into whatever budget is left. Hard-link groups are one
indivisible unit, charged once. A single file larger than one disc is detected and
refused rather than silently dropped.
Discs are packed by uncompressed size, so brb has to guess the compression
ratio before it compresses. It re-learns that guess from every finished disc.
2mksquashfsone self-contained image per disc
The image is a plain SquashFS 4.0 filesystem — zstd, 1 MiB blocks, xattrs preserved.
The file list arrives on stdin as NUL-delimited paths, so no filename can be
misparsed.
Encryption uses the age library in-process, not the age binary, so the format is
identical and one fewer thing has to be installed. Both the plaintext and ciphertext SHA-512 are computed in the same pass. The ciphertext
hash is the one that describes what is physically on the disc; the plaintext hash is
what proves the decryption came back right.
Backup needs public keys only, so no secret sits on the backup machine
for the days a set takes to build. The one exception is
--verify-roundtrip, which decrypts each image back and compares hashes
before the plaintext is deleted: that needs the identity present for the run, and it
is the only way to prove the set decrypts while you can still do something about it.
4par2recovery data over the ciphertext
Parity is computed over the encrypted bytes — the ones that will actually rot — at 10%
by default. The small files beside it (the hashes and the encrypted index) get their
own par2 set at 50%, because they are tiny and losing them is annoying.
ISO 9660 level 3 with Rock Ridge and Joliet. Level 3 multi-extent is what allows image
files above 4 GiB. Not UDF — xorriso cannot write it, so brb does not claim otherwise
on the disc. Every Linux system reads ISO 9660 anyway; some appliances that expect UDF
on Blu-ray may not.
ISOs are built as each disc goes into the drive and removed once it is written, so
staging stays near the size of the compressed set rather than roughly 2.2× that size for
the length of a burn campaign. verify-disc then reads the burned disc back
and checks every hash.
03 — what you can hold
What ends up on each disc.
Nothing on a disc is a private format. The image is a filesystem, the hashes are GNU
sha512sum files, the index is a two-column TSV, and the recovery data is par2. All of
it is legible to tools that predate brb by decades.
README.mdrestore instructions, written for a strangerMANIFEST.txtthe whole set, and the exact tool versions usedSHA512SUMShashes of every file on this discbrb.shthe bash reader — about a thousand linesbrb-linux-amd64static Go binary, 64-bit Intel/AMDbrb-linux-aarch64static Go binary, 64-bit ARMbrb-src.tar.gzcomplete source, dependencies vendoreddata/disc01.squashfs.agethe filesystem image, encrypteddisc01.squashfs.age.sha512hash of the encrypted imagedisc01.squashfs.sha512hash of the image AFTER decryptiondisc01.squashfs.age.par2par2 indexdisc01.squashfs.age.vol*.par2recovery data, 10%index.tsv.gz.agewhich disc holds which file, encryptedindex.tsv.gz.age.sha512hash of the encrypted indexsidecars.par2par2 index for the small filessidecars.vol*.par2recovery data for them, 50%
04 — the point of the design
Restoring without brb.
A backup format that needs its own software to read is a bet that the software survives. This one
does not take that bet. Given one disc and your age identity, the whole restore path is four
standard commands:
cpcoreutils
sha512sumcoreutils
par2only if it rotted
ageone static binary
mountthe kernel
cp /mnt/data/disc07.squashfs.age .
Take the image off the disc. If it will not read cleanly, use ddrescue — it
fills unreadable regions with zeros and keeps going, which is exactly what par2 needs.
age -d -i identity.txt -o disc07.squashfs disc07.squashfs.age
Decrypt it. A passphrase-protected identity works anywhere a plain one does.
mount -o loop,ro disc07.squashfs /mnt
Mount it. This step needs only the kernel.unsquashfs -d /dest disc07.squashfs extracts instead, if you would rather
have the files than the mount.
Two programs, one format
brb ships as two implementations with a deliberate split. The Go build is the tool you use
day to day. The bash script exists for one reason: a restore fifteen years from now, by
someone holding a disc and no particular reason to trust a static binary.
doctor, verify-disc, ingest, restore, mount, list, index
brb.sh refuses backup, plan, burn,
iso and init-key by name and says where they went, rather than
failing as an unknown command.
Held to the same format
A cross-compatibility suite builds a set with the Go build and reads it with both, then
asserts they produce identical answers — down to the encrypted index and the byte-for-byte
restored tree. It also runs the recipe printed on the disc itself, with neither
implementation involved.
Then it damages a set on purpose. A rotted .sha512 sidecar must not condemn an
image par2 proves is whole; an image par2 cannot repair must still be refused
rather than decrypted.
Where the two genuinely differ, the check is written the way it ought to pass and marked
XFAIL with the divergence named. An XFAIL that starts passing is
reported as XPASS and counted as a failure, so a fixed divergence gets
promoted to a real assertion instead of sitting in the ledger forever.
05 — keys
Public keys go in. Nothing secret has to.
Every line of the recipients file is an age recipient, every image is encrypted to all of
them, and any single matching identity restores the whole set on its own. Compromising the
backup machine yields nothing that outlives the compromise — unless you are running
--verify-roundtrip, which needs the identity there for the length of the run.
The price of that design is a single point of failure in the other direction: lose your
identity file and the archive is gone, permanently and irrecoverably. So back it up
somewhere that is not these discs — a password manager, and printed on paper. It is one
short line.
You can also give yourself a second way in: a rescue key. Mint a second keypair whose
private half never touches the disk in plaintext —
age-keygen | age -p -o ~/.config/brb/rescue-identity.txt.age
The identity goes from age-keygen's stdout straight into age -p's
stdin. It is never a file, so there is nothing to shred afterwards — which matters, because
shred cannot promise anything on a copy-on-write, compressed or
flash-translated filesystem. What lands on disk is a small file encrypted under a
passphrase you chose, and it is inert without that passphrase.
age-keygen prints the matching public key to the terminal on stderr, so the
pipe does not swallow it. That line is the half you have to keep, and appending it is a
separate step — nothing above did it for you:
echo 'age1...' >> ~/.config/brb/recipients.txt
Every disc built from that point on is encrypted to both keys, and either one restores the
whole set. Discs already burned are not — they were encrypted to the recipients that
existed when they were written.
The corollary: never put that file on these discs. A file stored next to
its own passphrase, or on the media it unlocks, is one secret, not two.
Why not just a passphrase?
The obvious request is "let me use a passphrase instead of a key file". brb deliberately does not allow it,
for three reasons.
age will not express "key OR passphrase" in one file
A passphrase in age is an scrypt stanza, and age refuses to mix an scrypt stanza
with recipient stanzas. Encrypting with a passphrase means giving up recipients
entirely — no multi-key sets, no rescue key, no third party who can restore for
your estate.
A stolen disc is ciphertext forever
Whoever holds the disc holds the attack surface, on their hardware, with unlimited
time. A passphrase a human can memorise loses that race over the decades this
medium is meant to last. An X25519 key does not.
Backup would need a secret on the machine
Today it needs only public keys, and the one flag that wants the identity is opt-in.
Passphrase encryption would make the ability to decrypt every disc a permanent
fixture of the backup host.
06 — media
Four disc geometries.
The image budget is what is left after reserving 2% for ISO 9660 overhead, 100 MiB for the
plaintext files carried on every disc, and room for the par2 recovery data. Raising redundancy takes
its share out of the same budget.
DISC_TYPE
Media
Raw capacity
Usable image budget
bd25
BD-R single layer
23.31 GiB
20.49 GiB
bd50
BD-R DL dual layer
46.61 GiB
41.07 GiB
bdxl100
BDXL triple layer
93.23 GiB
82.22 GiB
bdxl128
BDXL quad layer
119.21 GiB
105.16 GiB
Budgets assume the default PAR2_REDUNDANCY=10. A twenty-disc set takes days to build,
so backup records its progress after every completed disc: an interruption — a reboot,
a full disk, a Ctrl-C — costs only the disc that was in flight, and
brb backup --resume picks up at the next one.
The disc is the one variable brb does not control
brb writes a standard ISO and hands it to your burner. What that lands on is your choice,
and it is the only part of this that a format cannot help with — no amount of par2 saves a
disc whose recording layer has stopped holding a signal.
Blu-ray starts from a better position than the media most people mean by "burning a
backup". CD-R and DVD±R record into an organic dye, and dye is what fades.
BD-R HTL — the ordinary kind — already uses an inorganic recording layer
instead, so the classic failure of consumer optical media largely does not apply.
BD-R LTH is the exception: it is dye-based, it is usually the cheaper
shelf, and it is worth checking which one you are buying.
M-DISC
M-DISC BD-R is the archival grade of the same idea: an inorganic
layer the laser physically etches rather than bleaches. It burns and reads in any Blu-ray
drive, needs no flag and no different configuration, and nothing on this page changes if
you use it. It is sold as 25, 50 and 100 GB, so bd25,
bd50 and bdxl100 are the geometries available to you —
bdxl128 is not.
The thousand-year figure on the packaging comes from accelerated-ageing extrapolation
(ISO/IEC 16963), not from anyone having waited, and the honest gap between good HTL BD-R
and M-DISC is narrower than the marketing suggests. It buys better odds at a higher price
per gigabyte, which is a reasonable trade for a set you intend to shelve and forget. It
does not buy a different kind of promise — which is why every disc still carries 10% par2,
why the format is one the kernel can already mount, and why the only backup you know works
is one you have restored.
07 — get it
Install, then prove it restores.
Build
The Go build is the one to install — it is the only one that can write a set. Go 1.25 or
newer; a prebuilt static binary needs nothing at all.
cd go && go build -o ~/.local/bin/brb ./cmd/brb
The bash reader is a single self-contained script with nothing to build.
Install it under a distinct name — both name themselves after however they were invoked,
and one of them cannot back up.
install -Dm755 brb.sh ~/.local/bin/brb.sh
Dependencies
The distinction that matters is not between the two programs but between writing
discs and reading them. Writing needs more tooling, and you need it today.
Reading is what someone needs in fifteen years, and it is deliberately tiny.
sudo dnf install age squashfs-tools par2cmdline xorriso
On Debian and Ubuntu par2cmdline is par2 and ddrescue is
gddrescue; on Arch xorriso comes from libisoburn.
squashfs-tools must be 4.5 or newer — brb feeds mksquashfs a
NUL-delimited file list via -cpiostyle0, which does not exist in 4.4.
brb doctor tests for the flag directly rather than parsing a version string,
so trust it over any table.
A typical run
# dependencies, budgets, and can you decrypt it
brb doctor
# then back the key up OFF these discs
brb init-key
# how many discs, before committing to anything
brb plan
brb backup --verify-roundtrip
brb burn all
brb verify-disc 1
# once, before you trust the set
brb restore /tmp/testrestore
# staging held plaintext; wipe it
rm -rf /var/tmp/brb
Do the restore at least once, before you trust the set with anything. A backup you have
never restored is a hypothesis, not a backup.
Known limits
Not incremental. Every run is a full backup of the whole tree. A
completed set cannot be updated in place.
Staging holds plaintext while a run is in flight. Each image is
deleted as soon as it is encrypted, but one disc's worth is on disk at any moment. Put
staging on an encrypted volume, or wipe it afterwards.
A single file larger than one disc cannot be stored. brb detects this
during plan and backup, and stops rather than silently
dropping it.
ISOs are ISO 9660 level 3, not UDF. Any Linux system reads these;
some appliances that expect UDF on Blu-ray may not.