Storage
Storage manages disks, ZFS pools, datasets, snapshots and the memory limit of the ZFS cache (ARC). The page has seven tabs: Pools, Disks, Datasets, Snapshots, Spindown, Burn-in and ARC. Pools and disks refresh every 5 seconds (every 2 seconds while a scrub runs).
Bunkerbox uses OpenZFS from Debian contrib (zfs-dkms, zfsutils-linux). Without ZFS the page shows a notice; disks are still listed.
Disks
The table lists every disk with device, serial number, model, size (vendor units, 1 TB = 10¹² bytes), type (HDD with rotation rate, SSD, NVMe), temperature, SMART health and usage:
| Usage | Meaning |
|---|---|
| System disk | Carries /, /boot, /boot/efi, /usr, /var or active swap (also through LVM or LUKS), or belongs to a pool with the root filesystem. Never offered for pools. |
| Pool name | Member of an imported pool. |
| In use | Contains another filesystem, is mounted or belongs to an exported pool. Not offered, so no data is lost. |
| Free | Can be used for a new pool, another vdev or as a replacement. Leftovers of a destroyed pool count as free. |
SMART values come from smartctl -j -a -n standby and are read at most every 2 minutes. Sleeping disks are never woken up for this; the last known values stay visible. Read SMART again queries all disks immediately. Details shows firmware, power-on hours and all SMART attributes; suspicious attributes are marked red. Temperatures are marked yellow from 46 °C and red from 55 °C on HDDs, from 60 °C and 70 °C on SSDs.
Pools
Every pool is shown as a card with its health (ONLINE, DEGRADED, …), size, allocation, fragmentation, ashift, the last or running scrub or resilver, the scrub schedule, known data errors and the messages of zpool status (in the original English). Below is the device tree with read, write and checksum errors per disk.
Creating a pool
Create pool opens the layout builder:
- Recommended layout: Bunkerbox suggests pools for the free disks and applies them with one click. HDDs are grouped by size: 2 disks become a mirror, 3 to 5 a RAIDZ1, 6 to 12 a RAIDZ2, more are split into several RAIDZ2. Single disks next to redundant groups are left out because they would leave the whole pool unprotected. SSDs and NVMe become a mirrored pool
appsfor apps and Docker data. For the example hardware (2 × 16 TB, 5 × 5 TB, 2 × 1 TB SSD) this results intank= mirror 2 × 16 TB + RAIDZ1 5 × 5 TB andapps= mirror 2 × 1 TB. - Name, sector size and compression:
ashift=12(4 KiB) is the default and cannot be changed later. Compression defaults tolz4. - vdevs: choose type (stripe, mirror, RAIDZ1/2/3) and role (data, log, cache, hot spare, special) per vdev and tick its disks. Only free disks are offered.
- Check: while you edit, Bunkerbox shows raw capacity, usable capacity (before ZFS overhead) and fault tolerance. Errors (too few disks, system disk, disk in use, duplicate selection) prevent creation. Warnings must be confirmed explicitly:
- different redundancy of the data vdevs (e.g. a mirror next to a RAIDZ1;
zpoolrequires-ffor this), - data vdevs without redundancy,
- disks of different sizes, or HDDs and SSDs mixed in one vdev,
- RAIDZ vdevs that are too narrow or too wide, a special vdev without a mirror.
- different redundancy of the data vdevs (e.g. a mirror next to a RAIDZ1;
- Confirmation: type the pool name to create it. All data on the selected disks is erased.
New pools are always created with /dev/disk/by-id paths so they keep working after disks are recabled:
zpool create -o ashift=12 [-o autotrim=on] -O compression=lz4 -O atime=off -O xattr=sa \
-O acltype=posixacl -O dnodesize=auto -m /<name> <name> mirror /dev/disk/by-id/... ...
autotrim=on is set when all data disks are SSDs or NVMe. -f is only used after confirmed warnings; Bunkerbox refuses disks in use itself beforehand.
Scrub
Start scrub reads all data of the pool and repairs errors from redundancy; Stop scrub cancels it. No scrub can start during a resilver. Scrub schedule sets a weekly or monthly scrub (weekday or day 1–28 and hour, server time). Bunkerbox checks the schedules every minute; a slot missed during downtime runs once afterwards. If a scrub is already running, the slot is skipped. Note: Debian's zfsutils-linux ships its own monthly scrub (/etc/cron.d/zfsutils-linux), which may run in addition.
Swapping disks
- Offline takes a disk out of the pool (optionally only until the next reboot), e.g. before removing it. ZFS refuses this if the vdev would lose too many disks.
- Online brings it back.
- Replace swaps a disk for a free disk of at least the same size (
zpool replace). A resilver follows; the pool card shows its progress. Type the pool name to confirm.
Adding a vdev, export, import, destroy
- Add vdev extends the pool with a data, log, cache, spare or special vdev. The check compares its redundancy with the existing vdevs. Data vdevs cannot be removed from pools with RAIDZ again, so the pool name must be typed to confirm.
- Export unmounts the pool (
zpool export), e.g. before moving it to another system. - Import pool searches all disks for exported or foreign pools (
zpool import -d /dev/disk/by-id) and imports the selected one, optionally under a new name. - Destroy destroys the pool and all its data (
zpool destroy) after the pool name was typed. Its scrub and snapshot schedules are removed.
Export and destroy are refused while shares live on the pool, the pool carries the operating system or the Bunkerbox installation (/appdata/bunkerbox), apps with running containers keep their data on it or Docker has its data-root on it.
Datasets
The table shows all filesystems as a tree with used and available space, compression, record size, quota, mountpoint, number of snapshots and snapshot schedule. A * marks properties set on the dataset itself (not inherited).
Create dataset: choose the parent dataset, a name and a purpose. The purpose sets matching properties:
| Purpose | Properties |
|---|---|
| General | inherits everything |
| Media | recordsize=1M |
| Apps and databases | recordsize=64K |
| Documents | compression=zstd |
You can also set compression (off, lz4, zstd, zstd-fast, gzip, zle), record size (16K to 1M), quota, reservation, mountpoint and atime. Only values that differ from the parent dataset are set; everything else is inherited. An empty mountpoint means "inherited" (below the parent dataset), none means "not mounted". System directories (/, /etc, /usr, /var, /boot, /root, /tmp, …) and /mnt/remotes are not allowed as mountpoints.
Edit changes the same properties (zfs set, or zfs inherit for an emptied mountpoint). The mountpoint cannot change while shares, the Bunkerbox installation or apps with running containers live below it.
Mountpoints are checked before creating and changing: system directories and their parents (e.g. /mnt because of /mnt/remotes) are not allowed, nor is anything inside the Bunkerbox installation. A directory that already has something mounted on it or already contains files is never hidden – ZFS would otherwise make its content invisible.
Delete requires the full dataset name. If the dataset has child datasets or snapshots, deleting them must be ticked explicitly. The top dataset of a pool is only removed together with the pool; datasets with shares, in system directories, with the Bunkerbox installation, with data of running apps or with Docker's data-root are not deleted.
/appdata as a dataset
For the data of the Docker apps a dataset apps/appdata with mountpoint /appdata is intended (see Apps). Because Bunkerbox itself is installed in /appdata/bunkerbox, /appdata already is a non-empty directory. A dataset there would hide the running installation, so Bunkerbox refuses that and offers to take the content over instead:
- Datasets → New dataset, parent dataset
apps, nameappdata, mountpoint/appdata. - Enable "Copy existing content".
- All apps below
/appdatamust be stopped, and nothing else may be mounted below/appdata.
Bunkerbox then creates the dataset with overlay=on on a temporary mountpoint (/mnt/bunkerbox-migrate/…), copies the content of /appdata into it with cp -a (owners, modes, ACLs and extended attributes are kept) and only then mounts it on /appdata. If copying fails, the new dataset is removed again. The original files stay untouched underneath the dataset and keep using space on the system disk; they can be removed later while the dataset is unmounted. This works the same way for any other non-empty mountpoint.
Snapshots
Snapshots are read-only states of a dataset. The list can be filtered by dataset and shows name, age, used and referenced space.
- Create snapshot: choose dataset and name, optionally including all child datasets (
zfs snapshot -r). Names starting withauto-are reserved for schedules. - Roll back (
zfs rollback) restores the state of the snapshot; all later changes are lost. Type the full snapshot name (dataset@name) to confirm. If newer snapshots exist, they must be released explicitly with "Delete newer snapshots" (zfs rollback -r). - Delete removes a single snapshot. The full snapshot name has to be typed for confirmation; the server checks it as well.
Snapshot schedules
In the Datasets tab, Snapshots opens the schedule of a dataset: hourly (minute), daily (time), weekly (weekday and time) or monthly (day 1–28 and time), the number of snapshots to keep (1–1000) and optionally the child datasets. Automatic snapshots are named auto-YYYYMMDD-HHMM. After each automatic snapshot, the oldest automatic snapshots beyond the retention count are destroyed; manual snapshots are never deleted automatically.
ARC (ZFS memory cache)
ZFS uses free memory as a read cache (ARC). Without a limit the ARC can take so much RAM that Samba, Docker apps and the system run short. Bunkerbox therefore limits it automatically:
- Maximum = max(1 GiB, min(50 % of RAM, RAM − reserve)) with reserve = max(4 GiB, 25 % of RAM)
- Minimum = RAM / 32, at least 256 MiB, at most half of the maximum
| RAM | Maximum | Minimum |
|---|---|---|
| 4 GiB | 1 GiB | 256 MiB |
| 8 GiB | 4 GiB | 256 MiB |
| 16 GiB | 8 GiB | 512 MiB |
| 32 GiB | 16 GiB | 1 GiB |
| 64 GiB | 32 GiB | 2 GiB |
Set limits manually lets you choose the maximum (at least 512 MiB, at most RAM − 1 GiB) and minimum (at least 64 MiB, below the maximum) yourself; an empty minimum stays automatic. The effective values are applied immediately at service start and on every Apply through /sys/module/zfs/parameters/zfs_arc_max and zfs_arc_min, and saved in /etc/modprobe.d/bunkerbox-zfs.conf so they survive a reboot. If the ZFS module is loaded from the initramfs (root on ZFS or zfs-initramfs installed), the page shows a notice: run update-initramfs -u once.
The tab also shows the current ARC size, its target, the limits in effect and the hit rate since boot (from /proc/spl/kstat/zfs/arcstats). Size and hit rate also appear on the dashboard.
Notifications
Every 5 minutes Bunkerbox checks pools and disks and reports through notifications: degraded (DEGRADED) and failed pools (FAULTED, UNAVAIL, SUSPENDED, REMOVED), read, write or checksum errors of a disk, scrubs with errors, a failing SMART status, growing defect counters (reallocated, pending or uncorrectable sectors) and hot disks (HDD from 55 °C, SSD and NVMe from 70 °C). The same message is not repeated while the condition does not change.
Safety
- All commands (
zpool,zfs,smartctl,lsblk) are started with argument lists and no shell. Pool, dataset and snapshot names, disk ids and paths are validated strictly first;zpoolonly receives device paths that Bunkerbox determined itself. - Destructive actions (creating or destroying a pool, adding a vdev, replacing a disk, deleting a dataset, rolling back) require the typed confirmation. The service checks it, not only the interface.
- System disks and disks in use are never offered and are refused by the service.
- Snapshots are only destroyed with a name of the form
dataset@name, so a dataset is never destroyed by accident. - While an operation runs (e.g. creating or destroying a pool), updates are held back.
Mock mode
In mock mode (BUNKERBOX_MOCK=1) Bunkerbox simulates the example hardware: a 240 GB system disk, 2 × 16 TB HDD and 5 × 5 TB HDD in pool tank (mirror + RAIDZ1), 2 × 1 TB SSD in pool apps (mirror), two new, free 18 TB HDDs (sdk, sdl) for the burn-in with the datasets tank/media, tank/documents, tank/backup, tank/home and apps/appdata (/appdata), some snapshots, schedules and 32 GiB RAM. All actions work but only change the simulated state; a scrub takes 15 minutes, a resilver 3 minutes. The simulated state starts fresh on every start.
Spindown
The Spindown tab lists every rotating disk (HDD) except the system disk with its usage, current power state (Active, Idle, Asleep), the time without access and the configured idle time. SSDs, NVMe disks and the system disk are never spun down.
- State without waking: Bunkerbox reads the state every minute with
smartctl -i -n standby(fallback:hdparm -C). Neither command wakes a sleeping disk; the SMART values on the Disks tab are read with-n standbyas well. - Configure sets per disk whether and after how many minutes without reads or writes it is spun down (5–720 minutes), and the method:
- Bunkerbox idle monitor (default): every minute Bunkerbox reads the sectors read and written from
/sys/block/<disk>/stat(SMART queries are not counted there). If they do not change for the configured time, Bunkerbox sends the disk to sleep withhdparm -y. Works with most disks, including USB adapters. - Disk firmware timer (
hdparm -S): the disk spins down by itself. The firmware only knows 5-second steps up to 20 minutes, 21 minutes and 30-minute steps up to 5.5 hours above that; Bunkerbox rounds up. Because some disks forget the value after a power loss, Bunkerbox sets it again on start and whenever the disk is reconnected. When the method is changed or spindown is switched off, Bunkerbox resets the timer to 0.
- Bunkerbox idle monitor (default): every minute Bunkerbox reads the sectors read and written from
- Spin down now sends a disk to sleep immediately (
hdparm -y). It wakes up automatically on the next access; that takes a few seconds. - Never during scrub, resilver or burn-in: while a scrub or resilver runs on the pool of a disk or a burn-in runs on the disk, Bunkerbox does not spin it down and refuses Spin down now; the table shows the reason. A firmware timer does not trigger in that time anyway because the disk is read constantly.
- The settings are kept in
data/spindown-state.json(600) under the stable disk id (/dev/disk/by-id), so they still apply when the kernel name (sdb) changes. The Disks tab shows the power state too.
Tip: frequent spin-ups wear disks. Do not spin down pools with regular access (apps, databases); 20–60 minutes are common for archive pools.
Burn-in
New disks fail most often in their first weeks. The Burn-in tab therefore tests new disks thoroughly before they go into a pool. Only free disks (no pool, no partitions or filesystems) are offered, never the system disk; the service checks this itself as well.
A burn-in consists of three tests, each of which can be deselected:
| Test | Command | Duration (18 TB) |
|---|---|---|
| SMART short test | smartctl -t short | about 2 minutes |
| Write test | badblocks -w -s -v (four patterns 0xaa, 0x55, 0xff, 0x00, each written and read back) | several days |
| SMART long test | smartctl -t long | one to two days |
- Start: Start burn-in shows model, serial number, size and by-id path. To confirm, type the serial number of the disk. The write test destroys all data on the disk irrevocably.
- Safety: right before the write test Bunkerbox checks again that the same disk (serial number and size) is behind the path, that it is still free and that no
badblocksruns on it. Writing uses the/dev/disk/by-idpath. Disks with a running burn-in are refused by create pool, add vdev and replace disk, and are never spun down. Disks above 16 TiB are tested with 8 KiB blocks becausebadblockscan only count 2³² blocks. - Progress: running burn-ins appear as a card with the overall progress, the progress of every test (pass n of 8 in the write test), the bad blocks found so far and the expected end. Log shows the commands, phase changes, bad blocks and SMART changes. Several disks can be tested at the same time.
- Result: before and after the burn-in Bunkerbox reads the SMART counters (reallocated, pending and uncorrectable sectors, reported read errors, CRC errors, media errors on NVMe). A disk fails on bad blocks, increased defect counters, a failed self-test or a failing SMART status. If only CRC or timeout counters increase, it passes with a hint to check the cabling. When a test fails, the following ones are skipped. When the disk refuses a SMART test (common with USB adapters), the test is skipped and the burn-in is judged by the other tests.
- Burned in: after a passed burn-in the disk carries the mark Burned in on the Disks tab and in the pool builder, as long as the serial number matches and no later burn-in failed. The builder points out disks that are not burned in yet but does not prevent using them.
- Cancel stops the running test (
badblocksis terminated, a self-test aborted withsmartctl -X). The disk is then not considered burned in. - History: the last 50 burn-ins are kept with result, duration, bad blocks, SMART changes and log in
data/burnin-history.json(600). If Bunkerbox restarts during a burn-in, the run counts as interrupted (badblocksdoes not continue). - Updates are held back while a burn-in runs (including automatic updates and
update.sh). - Notification: at the end of a passed or failed burn-in the event Burn-in finished is raised (see Notifications).
In mock mode a full burn-in takes about four minutes; the second new 18 TB disk (sdl) develops three bad blocks and fails.