Skip to content

Releases: jzbz/brb

v0.3.0

Choose a tag to compare

@jzbz jzbz released this 16 Oct 07:14
v0.3.0
41f1b48

A dependency and toolchain release: age v1.3.2, and binaries built with Go
1.26.9. The on-disc format is untouched: a set written by 0.1.0, 0.1.1 or 0.2.0
reads identically here, and one written here reads on any of them. The two
readers remain interchangeable disc for disc wherever the installed age is
v1.3.2 or newer; where it is older, the exceptions are below.

Upgrade for these

  • brb doctor, backup and init-key no longer print a secret key that was
    put in the recipients file by mistake. age v1.3.1 quoted a line it could not
    parse in its error, so a misplaced AGE-SECRET-KEY-… line reached the
    terminal in full; v1.3.2 gives only its line number. A secret key on the same
    line as a public key was quoted by both, and brb now names that line without
    quoting it.
  • A recipients or identity file larger than 16 MiB is refused, and an identity
    file is refused as too long rather than as holding a damaged key. 0.2.0 read
    only the first 16 MiB: a recipient after that point was left out of the
    encryption without a word, though MANIFEST.txt still listed it, and an
    identity after it was never tried.
  • Reading a hostile or damaged disc: the Go build refuses an age header over
    2 MiB or with more than 1024 recipient stanzas. It parses the header before it
    compares the ciphertext's checksum, and 0.2.0 held the whole header in memory
    while reading it: about three times its length for one long stanza, and more
    than thirty times for a header made of many small ones.

Refused here, accepted by 0.2.0

Which is why this is the minor and not the patch:

  • recipients and identity files over 16 MiB, as above;
  • when reading, an age header over 2 MiB, with more than 1024 recipient
    stanzas, or with a stanza of more than 128 arguments;
  • a stray carriage return inside the short last line of an armored,
    passphrase-protected identity file. CRLF line endings are still accepted, and
    one inside a full-length line was refused before;
  • building from source with Go 1.25 and no network access. Go 1.26.0 or newer
    is required; with network access, Go 1.25 fetches it by itself under its
    default settings.

The reading limits are age v1.3.2's. brb.sh never reads the recipients file,
and hands decryption to the age that is installed, so it applies them wherever
that is v1.3.2 or newer, and not where it is older.

A set encrypted to more than 1024 recipients is still written, without a
warning, but the Go build of this release cannot decrypt it, and neither can
age v1.3.2. brb 0.2.0, and brb.sh with age v1.3.1 or older, still can.
brb doctor reports such a configuration as unreadable when it can read the
identity; with a passphrase-protected or missing identity it says the round
trip was not proved. backup --verify-roundtrip refuses it after building and
encrypting the first disc.

Building from source

The source tarball rebuilds with no network access on any Go from 1.26.0, under
Go's default settings. CI now checks exactly that on every run, on the oldest Go
the module accepts.

Toolchain

The binaries are built with an upstream Go 1.26.9 and carry age v1.3.2,
filippo.io/hpke v0.4.0 and golang.org/x/crypto v0.57.0; the amd64 binary also
links golang.org/x/sys v0.48.0, which the aarch64 build does not use. Both
rebuild byte for byte from the source tarball with that toolchain. None of the standard-library
advisories fixed in Go 1.26.9 and 1.27.2 reach brb on Linux, so the 0.2.0
binaries are not affected by them either. govulncheck lists one of them,
GO-2026-6604 in os.Root, against brb's source; that advisory applies to
Windows only.

The 0.2.0 binaries were built with a distribution's Go 1.27.0, which enables the
nodwarf5 experiment and so records its version as go1.27.0-X:nodwarf5, a
string govulncheck cannot parse; scanning a 0.2.0 binary therefore silently
skips the standard library. These scan normally. The one result, the
golang.org/x/crypto openpgp notice, appears because a stripped binary cannot
show which packages it links; brb does not link openpgp.

Known limitation

Unchanged from 0.2.0: staging can still run out mid-image when an adaptive pack
ratio has pushed the raw budget well above the image budget. It is warned about
before any disc whose raw content exceeds the free space in staging, finished
discs survive it, and a resume completes once space exists.

Verifying this download

gpg --verify SHA256SUMS.asc SHA256SUMS
sha256sum -c SHA256SUMS

The signature is PGP key 249738C8641C3359. SHA256SUMS covers all four
artifacts; there is no second hash file, since an unsigned one beside a signed
one is a downgrade path.

v0.2.0

Choose a tag to compare

@jzbz jzbz released this 10 Sep 20:13
v0.2.0
b63df73

Fixes to the writer, both readers and the configuration loader. The on-disc
format is untouched: a set written by 0.1.0 or 0.1.1 reads identically here, one
written here reads on either of them, and the two readers remain interchangeable
disc for disc.

Upgrade for this one

A resume no longer produces a set that cannot be restored. A source path that
had become a directory since the interrupted run was never recognised as already
written, so the run finished, reported a complete set, and produced one that
unsquashfs abandons part way through restoring, recovering a fraction of the
archive. Such a resume is now refused, naming the paths and what they have
become.

Reading a hostile or damaged disc

  • ingest no longer follows symbolic links off a disc into staging. A disc whose
    data/ pointed elsewhere had that directory's files staged as images, and a
    MANIFEST.txt pointing at a device streamed until the filesystem filled. Both
    readers refuse the same discs now, and say the link is why.
  • brb.sh no longer truncates whatever a symlinked staging lock points at. A
    shell redirection has no O_NOFOLLOW; the name is tested first, as the Go
    build's open has always done.
  • A manifest's disc count is read as a decimal number rather than a shell
    expression. discs : 010 was octal eight, so eight staged images satisfied a
    ten-disc set and a partial set was announced complete.
  • A public archive's identity.txt cannot slip past its size gate as a device
    node, and the disc numbers a reader is handed are bounded before they reach
    arithmetic that wraps silently.

Configuration

Values that 0.1.1 accepted are refused here, which is why this is the minor and
not the patch. A configuration carrying any of them must be corrected before
anything runs:

  • KEEP_IMAGES and ASSUME_YES take the eight boolean spellings and nothing
    else. A 2, an 007 or a padded " 1" was read as true by the Go build and
    killed brb.sh outright on the same file.
  • $'...', $"...", $- and $[...] are refused anywhere in a value. The
    shell expands them and the Go parser kept them as literal text, so one file
    named two different directories — STAGING among them.
  • ARCHIVE_NAME is bounded at the 128 bytes a disc's publisher field holds.
    Longer names built the whole set and then failed every burn and iso.
  • PACK_RATIO_MARGIN and JOBS have upper bounds, and BLOCK_SIZE is used as
    it was validated rather than with whitespace the check had trimmed.

ASSUME_YES in the environment now beats the configuration file in brb.sh, as
it already did in the Go build. It answers the confirmation in front of a restore
that overwrites its destination, and the two readers had it in opposite orders.

Also

A resume says so when a new hard link duplicates data an earlier disc already
holds, doctor reports the budget the packer will actually use, and the
recovery-block count is the one par2 writes rather than a truncated estimate.

Known limitation

Staging can still run out mid-image when an adaptive pack ratio has pushed the
raw budget well above the image budget: preflight sizes its refusal from the
image budget, which is a floor and not a bound on the plaintext mksquashfs
writes. Unchanged from 0.1.1, and now warned about before each disc rather than
fixed. Finished discs survive it and a resume completes once space exists.

Verifying this download

gpg --verify SHA256SUMS.asc SHA256SUMS
sha256sum -c SHA256SUMS

The signature is PGP key 249738C8641C3359. SHA256SUMS covers all four
artifacts; there is no second hash file, since an unsigned one beside a signed
one is a downgrade path.

v0.1.1

Choose a tag to compare

@jzbz jzbz released this 22 Aug 22:05
v0.1.1
32ac77a

A maintenance release. Same on-disc format, and a set written by 0.1.0 reads
identically here.

What changed

Filenames can no longer drive your terminal. Both readers escaped C0
controls and DEL but passed the C1 controls through in both of their spellings,
so a filename holding U+009B reached an xterm as CSI through index and list.
Both now decode UTF-8 to tell a C1 control from a continuation byte — U+4E9B
ends in 0x9b and must survive intact — and the two are held to byte-identical
output by the cross-implementation suite.

brb.sh no longer needs a few hundred megabytes twice. It read
unsquashfs -ll whole into a variable and then parsed that into an array, so
both existed at once; on a million-file disc that is the difference between a
restore working and not, on exactly the low-RAM rescue machine the bash reader
exists for. It streams now, carrying the pipeline's exit status in-band so the
listing is still disbelieved unless the tool succeeded.

A hostile disc can no longer exhaust memory during ingest. brb.sh read a
disc-supplied SHA512SUMS with no line cap where the Go reader has always
stopped at 2²⁰. A directory posing as a disc has no size limit.

A short write during a restore is an error again. Of three chunked copy
loops, one had lost the io.ErrShortWrite check its siblings carry, so a copy
that stopped early would have written a truncated file and reported success.
All three now share one implementation that keeps it.

One config file drives both readers. KEEP_IMAGES and ASSUME_YES are
keys brb.sh has always read and the Go build refused outright — which meant
the sample configuration in the README would not load. Both are accepted and
honoured now. Note that ASSUME_YES=1 answers every confirmation, including
the one before a restore overwrites its destination.

MANIFEST.txt records paths that could not be read. They were reported once
to a terminal that is long gone by the time anyone reads the discs, and a
restore quietly missing a file looks exactly like a backup that never had it.

doctor stops recommending pv, which nothing in either implementation has
ever run, and reports flock, which brb.sh does run.

The README no longer claims the two readers behave identically. They restore
identically — same files, same bytes, same refusals — but they differ around the
edges, and those differences are a table now, every row of it checked by running
both. The destination-writability caveat on restores is stated as well.

With this release the suite's ledger of known divergences between the two
implementations is empty for the first time.

brb bin-packs a directory tree into disc-sized groups using first-fit
decreasing packing, builds one self-contained SquashFS image per disc, encrypts
it with age, computes par2 recovery data over the
ciphertext, and images each disc as ISO 9660 for BD-R, BD-R DL or BDXL — built
on demand as you burn.

Every disc is independent: losing one loses only the files that lived on it,
and every disc carries the full directory structure of the original tree.
Restoring needs nothing but the Linux kernel and standard tools — repair with
par2, decrypt with age, mount the SquashFS. No brb, no Python, no bespoke
archive format.

Why 0.1.x

The number describes the tool, not the discs. Both readers are held to the same
on-disc format by a cross-implementation suite that builds a set with the Go
build and reads it with both, but the format has not earned a stability promise
yet, and neither has a 1.0. A disc written by this release is permanent
whatever the version says, and carries its own reader.

Artifacts

File What it is
brb-v0.1.1-linux-amd64 static binary, x86-64 — writes and reads
brb-v0.1.1-linux-aarch64 static binary, aarch64 — writes and reads
brb-v0.1.1.sh bash reader: doctor, ingest, restore, mount, list, index, verify-disc. It deliberately cannot write a set
brb-v0.1.1-src.tar.gz complete source, dependencies vendored, rebuilds with no network
SHA512SUMS hashes of the four above

Verify what you downloaded:

sha512sum --ignore-missing -c SHA512SUMS

The binaries are static — no libc, no interpreter, no shared libraries. The
source tarball rebuilds offline:

tar xzf brb-v0.1.1-src.tar.gz && cd brb-0.1.1/go && go build -mod=vendor ./cmd/brb

Requirements

Linux. Writing a set needs mksquashfs and unsquashfs (squashfs-tools 4.5 or
newer — 4.4 lacks -cpiostyle0 and is refused, which rules out Debian 11 and
Ubuntu 20.04), par2 and xorriso. It needs no age binary: encryption uses
filippo.io/age as a library. A restore through brb needs only unsquashfs
and par2; the final step can instead be a kernel mount,
sudo mount -o loop,ro disc07.squashfs /mnt (the images are zstd-compressed,
so kernel 4.14 or newer). Restoring with brb.sh, or by hand, also needs the
age binary. Building from source needs Go 1.25 or newer.

Installation

go install does not work on this repository, and no spelling of it does:
go/go.mod declares the module path as github.com/jzbz/brb while the module
lives in the repository's go/ subdirectory. Take a binary above, or build
from a checkout: cd go && go build -o ~/.local/bin/brb ./cmd/brb.

Generate a keypair before the first backup — brb init-key, or
brb init-key --rescue-key for a second, passphrase-protected identity.
brb backup refuses to start without a recipients file.

Before you rely on this

Test a restore before trusting a set. Every run is a full backup, not an
incremental one, and a completed set cannot be updated in place. A restore
overwrites its destination, mode and mtime included. Lose the age secret key
and the archive is unrecoverable — it is never written to a disc. The set is
not signed; the format has no signing key. See the README's Limitations section
for the rest.

MIT licensed.

v0.1.0

Choose a tag to compare

@jzbz jzbz released this 21 Aug 19:55
v0.1.0
53db336

First tagged release.

brb bin-packs a directory tree into disc-sized groups using first-fit
decreasing packing, builds one self-contained SquashFS image per disc, encrypts
it with age, computes par2 recovery data over the
ciphertext, and images each disc as ISO 9660 for BD-R, BD-R DL or BDXL — built
on demand as you burn.

Every disc is independent: losing one loses only the files that lived on it,
and every disc carries the full directory structure of the original tree.
Restoring needs nothing but the Linux kernel and standard tools — repair with
par2, decrypt with age, mount the SquashFS. No brb, no Python, no bespoke
archive format.

Why 0.1.0

The number describes the tool, not the discs. Both readers are held to the same
on-disc format by a cross-implementation suite that builds a set with the Go
build and reads it with both, but the format has not earned a stability promise
yet, and neither has a 1.0. A disc written by this release is permanent
whatever the version says, and carries its own reader.

Artifacts

File What it is
brb-v0.1.0-linux-amd64 static binary, x86-64 — writes and reads
brb-v0.1.0-linux-aarch64 static binary, aarch64 — writes and reads
brb-v0.1.0.sh bash reader: doctor, ingest, restore, mount, list, index, verify-disc. It deliberately cannot write a set
brb-v0.1.0-src.tar.gz complete source, dependencies vendored, rebuilds with no network
SHA512SUMS hashes of the four above

Verify what you downloaded:

sha512sum --ignore-missing -c SHA512SUMS

The binaries are static — no libc, no interpreter, no shared libraries. The
source tarball rebuilds offline:

tar xzf brb-v0.1.0-src.tar.gz && cd brb-0.1.0/go && go build -mod=vendor ./cmd/brb

Requirements

Linux. Writing a set needs mksquashfs and unsquashfs (squashfs-tools 4.5 or
newer — 4.4 lacks -cpiostyle0 and is refused, which rules out Debian 11 and
Ubuntu 20.04), par2 and xorriso. It needs no age binary: encryption uses
filippo.io/age as a library. A restore through brb needs only unsquashfs
and par2; the final step can instead be a kernel mount,
sudo mount -o loop,ro disc07.squashfs /mnt (the images are zstd-compressed,
so kernel 4.14 or newer). Restoring with brb.sh, or by hand, also needs the
age binary. Building from source needs Go 1.25 or newer.

Installation

go install does not work on this repository, and no spelling of it does:
go/go.mod declares the module path as github.com/jzbz/brb while the module
lives in the repository's go/ subdirectory. Take a binary above, or build
from a checkout: cd go && go build -o ~/.local/bin/brb ./cmd/brb.

Generate a keypair before the first backup — brb init-key, or
brb init-key --rescue-key for a second, passphrase-protected identity.
brb backup refuses to start without a recipients file.

Before you rely on this

Test a restore before trusting a set. Every run is a full backup, not an
incremental one, and a completed set cannot be updated in place. A restore
overwrites its destination, mode and mtime included. Lose the age secret key
and the archive is unrecoverable — it is never written to a disc. The set is
not signed; the format has no signing key. See the README's Limitations section
for the rest.

MIT licensed.