braid add
Add one or more disks to the braid pool. Creates a new pool if none exists, or expands an existing one.
When to use it
- Setting up a new NAS (bootstrap with one or more disks)
- Expanding storage by adding a new drive to an existing pool
Basic example
sudo braid add toshiba1=/dev/disk/by-id/ata-TOSHIBA_MN07ACA12T_1234
Common variations
Bootstrap a new pool with two disks (creates RAID1 immediately):
sudo braid add \
toshiba1=/dev/disk/by-id/ata-TOSHIBA_MN07ACA12T_1234 \
toshiba2=/dev/disk/by-id/ata-TOSHIBA_MN07ACA12T_5678
Add a disk to an existing pool:
sudo braid add toshiba3=/dev/disk/by-id/ata-TOSHIBA_MN07ACA12T_9012
Preview what would happen without making changes:
sudo braid add toshiba1=/dev/disk/by-id/ata-TOSHIBA_MN07ACA12T_1234 --dry-run
Skip the confirmation prompt (for scripting):
sudo braid add toshiba1=/dev/disk/by-id/ata-TOSHIBA_MN07ACA12T_1234 --yes
Pass passphrase non-interactively:
echo -n 'hunter2' | sudo braid add toshiba1=/dev/disk/by-id/ata-TOSHIBA_MN07ACA12T_1234 --passphrase-stdin
sudo braid add toshiba1=/dev/disk/by-id/ata-TOSHIBA_MN07ACA12T_1234 --passphrase-file /tmp/pass.txt
Enroll a keyfile for auto-unlock from a mounted USB drive at the same time:
sudo braid add toshiba1=/dev/disk/by-id/ata-TOSHIBA_MN07ACA12T_1234 --enroll /mnt/usb
Mount the USB first so the --enroll directory refers to removable media,
not persistent host storage.
Important flags
| Flag | Purpose |
|---|---|
--dry-run | Show what would happen without executing |
--yes | Skip interactive confirmation |
--passphrase-stdin | Read passphrase from stdin instead of TTY prompt |
--passphrase-file <path> | Read passphrase from a file (conflicts with --passphrase-stdin) |
--enroll <dir> | Enroll braid.key from this directory into LUKS slot 1 on new disks |
--luks-format-arg=<ARG> | Advanced: pass one raw argument to cryptsetup luksFormat, repeated once per argument; always use the equals form (e.g. --luks-format-arg=--pbkdf). braid refuses flags it manages itself – identity, key-material, integrity, and on-disk-layout options such as --uuid, --label, --type, --key-file, and offset/sizing flags. |
--progress auto|always|never | Control progress display (default: auto) |
Disk spec format
Each disk is specified as NAME=PATH, where:
- NAME is a short label you choose (e.g.
toshiba1) - PATH is the
/dev/disk/by-id/stable device path
The name is stored in pool.json and used in LUKS mapper names (braid-toshiba1), LUKS labels, and all future commands. The persistent member identity is the LUKS UUID, not the name.
What happens under the hood
-
Probes each disk to determine its state (fresh, braid-labeled, or foreign)
-
Shows a confirmation prompt with the disk’s name and by-id path, plus its model/size/serial (best-effort from the live device via lsblk – omitted if unavailable)
-
For fresh disks: pre-generates a LUKS UUID, LUKS-formats the disk with the pool passphrase and
braid-<name>label, creates a LUKS header backup, and opens the LUKS mapperSee Pending LUKS header backups – copy each
.luksheaderoff-system and delete the local copy. -
If no pool exists: creates a btrfs filesystem (RAID1 if 2+ disks, single if 1 disk; braid explicitly pins the
block-group-treefeature bit so that bit is visible and stable across toolchain versions – see ADR-027) -
If a pool exists: writes a phased UUID-keyed journal, adds the device to the existing btrfs filesystem, records the new membership in pool.json, then advances the journal to the balance phase
-
If the pool now has 2+ disks: balances data to RAID1, then clears the journal – unless the pool has a missing device, in which case the balance is skipped (a
[skip]note explains why). Redundancy is restored later byremove-missingorreplace, not by the degraded add.
A sleep inhibitor is held during all irreversible operations to prevent the system from suspending mid-operation.
If a btrfs exclusive operation (a running balance, device add/remove/replace, resize, or swap activate) is already in flight on the pool, braid does not fail – its btrfs commands queue behind the in-flight operation (via --enqueue) and the kernel runs them when the pool is free. A paused balance is the exception and is refused (see Safety checks below).
Disk acceptance rules
braid classifies each disk before acting:
- Fresh disk (no LUKS): accepted. LUKS-formatted with the pool passphrase.
- Returning braid disk (braid-labeled LUKS, btrfs FSID matches the pool): accepted as a recovery add. The disk is re-joined to the pool without reformatting. If the old btrfs signature would make
btrfs device addrefuse the disk, braid first runs the narrow wipewipefs --all --types btrfson the verified mapper, then usesbtrfs device add -f. - Non-braid LUKS: refused. braid will not adopt a LUKS device it did not create.
- Braid-labeled, wrong pool: refused. The disk belongs to a different btrfs filesystem.
- Braid-labeled, no btrfs superblock: refused. The disk’s identity is ambiguous (could be partial init, clean eviction, or manual wipe). Wipe the disk and add as fresh.
- Braid-labeled, pool not mounted: refused during bootstrap. Identity cannot be verified without a mounted pool.
Safety checks / refusal cases
- Rejects duplicate disk names in the same command
- Rejects disks that conflict with existing pool membership (same LUKS UUID, same name, or same by-id path)
- Rejects absent disks (not plugged in)
- Verifies the passphrase against an existing pool member before formatting new disks
- Warns if the pool has missing devices but does not refuse:
braid addstill adds the new disk, but skips the RAID1 convert balance (it surfaces a[skip]note), so the pool stays degraded and redundancy is not restored at add time. To repair, either runbraid replace --old <missing-name> --new <new-name>=/dev/disk/by-id/<...>to swap in a new disk for the missing member, or – on a 2-disk degraded pool whereremove-missingalone would refuse (it cannot drop RAID1 below two devices) – runbraid addthenbraid remove-missingto drop the dead member and rebalance onto the new disk. - Warns if existing pool drives have a keyfile but
--enrollwas not passed - Refuses if a pending operation journal (
pending-op.json) exists – runbraid recoverto reconcile. - Refuses if another braid operation is in progress (pool lock
/run/braid-pool.lockis held) – retry once it finishes. - Refuses if a btrfs balance is paused on the pool – resume or cancel it first. A paused balance holds the exclusive-operation lock indefinitely, so braid cannot wait it out.
- Refuses when UPS support is enabled and
braid ups statuscannot verify a trustedOL(utility-power) state.
Interrupted adds
Existing-pool adds recover in two phases:
- PoolMutation: disk preparation and btrfs membership are not fully committed yet.
braid recovermay finish formatting a fresh target, re-open a verified returned target, run the narrow btrfs-signature wipe for that returned target, and runbtrfs device add. - PostAddBalanceRaid1: membership and
pool.jsonare committed.braid recoverwill not format, wipe, or add disks in this phase; it only mounts/probes the committed pool, repairspool.jsonfrom the committed live topology if needed, runs the owed RAID1 balance when btrfs balance state is idle, and clearspending-op.jsonafter that succeeds. A paused, running, or unknown balance state fails closed with the journal preserved.
Related commands
- braid status – check pool health and disk info
- braid remove – remove a live disk from the pool
- braid replace – replace a dead or live disk
- braid unlock – open LUKS devices and mount the pool
Related guides
- Getting started – initial setup walkthrough