Table of Content

useful links

https://wiki.archlinux.org/index.php/ZFS

Pool handling

ComandsDescription
zpool statusgives status over all pools
zpool scrub ${pool}initiate a scrub. Should be done on a regular basis. Takes ages on big pools.
zpool scrub -s ${pool}cancel a running scrub
zpool create -o ashift=<SHIFTsize> -m none <pool_name> <UUID_of_disk>create a new storage pool with one disk
zpool attach <pool_name> <UUID_of_disk1> <UUID_of_disk2>adds a second disk to a “normal” zpool and converts it to a mirror pool
zpool create -m none main raidz1 ${spaceSepUUIDs}create a new storage pool called main. Make sure you use /dev/disk/by-id
zpool create -o ashift=12 -m /mnt/main main raidz1 ${spaceSepUUIDs.}use ashift=12 for HDDs with 4k Sektors
zpool create -o ashift=13 -m none ssd mirror ${spaceSepUUIDs.}mirror use ashift=13 for SSDs with 8k Sektors
zpool import -d /dev/disk/by-id ${pool}do not import without -d /dev/disk/by... otherwise it will import it using /dev/sd...
zpool destroy ${pool}destroy a pool
zpool export ${pool}export a pool to e.g. use it on another system
zpool import -f -d /dev/disk/by-id ${pool}force import if you forgot to export it
zfs set mountpoint=/foo/bar ${pool}set the mountpoint for a pool
blockdev --getpbsz /dev/sdXYprint sector size reported by the device ioctls
lsblk -tprint physical and logical sector size of all disks

Dataset handling

ComandsDescription
zfs listlists all datasets and their mountpoint
zfs create ${pool}/${dataset}create a dataset
zfs create -o recordsize=8K -o primarycache=metadata -o logbias=throughput ssd/database
zfs set quota=20G ${pool}/${dataset}set quota of dataset to 20G
zfs set mountpoint=/foo/bar ${pool}/${dataset}set the mountpoint for a dataset
zpool destroy ${pool}/${dataset}destroy a dataset

Encrypted Datasets

ComandsDescription
zfs create -o encryption=on -o keyformat=passphrase ${pool}/${dataset}Create a dataset with native default encryption (currently AES-256-gcm) and passphrase
dd if=/dev/random of=/path/to/key bs=1 count=32create a key to use for keyformat=raw
zfs create -o encryption=on -o keyformat=raw -o keylocation=file:///path/to/key ${pool}/${dataset}Create a dataset using a raw key

Auto unlock of encrypted datasets

If you want to get your encrypted datasets auto unlocked while booting you could create a systemd service which performs the action for you.

You just need ot make sure that the passphrase is somehow accessable in this state.

Lets assume that you have created your dataset with keyformat=raw and lets assume you have only one zpool.

First create a generic systemd serivce file which you can use for this, something like this (if you don’t get it by installing zfs already):

$ cat /etc/systemd/system/zfs-load-key@.service
[Unit]
Description=Load ZFS keys
DefaultDependencies=no
Before=zfs-mount.service
After=zfs-import.target
Requires=zfs-import.target

[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/usr/sbin/zfs load-key <zpool_name>/%I

[Install]
WantedBy=zfs-mount.service

Next could be that you just create a soft link like:

$ ln -s zfs-load-key@.service zfs-load-key@<zfs_dataset_name>.service

This will require to reload systemd and of course enable the service so that it runs while your system is booting.

$ systemctl daemon-reload
$ systemctl enable zfs-load-key@<zfs_dataset_name>.service

But what happens if you have more then one zpool and you want to auto unlock them.

systemd can deal make use of parameters only via the attribute EnvironmentFile.

This means that you would need to prepare for each zfs dataset such an config file.

cat /etc/zfs/service_config/<zpool_name>_<zfs_dataset_name>
zpool_name=<zpool_name>
zfs_dataset_name=<zfs_dataset_name>

Sample:

$ cat /etc/zfs/service_config/data_pool-picture_dataset
zpool_name=data_pool
zfs_dataset_name=picture_dataset

This brings us the benefit, that we can use %I in the service name as the file name:

$ cat /etc/systemd/system/zfs-load-key@data_pool-picture_dataset.service
[Unit]
Description=Load ZFS keys
DefaultDependencies=no
Before=zfs-mount.service
After=zfs-import.target
Requires=zfs-import.target

[Service]
Type=oneshot
EnvironmentFile=/etc/zfs/service_config/%I
RemainAfterExit=yes
ExecStart=/usr/sbin/zfs load-key $zpool_name/$zfs_dataset_name

[Install]
WantedBy=zfs-mount.service

And from now on it is very easy to auto unlock multible zfs datasets located in multible zpools.

Side note, of course you could also use service config file to specify different comands to execute, e.g. if you require a yubikey or need to get the pwd from your pass/gpg.

$ cat /etc/zfs/service_config/data_pool-picture_dataset
zpool_name=data_pool
zfs_dataset_name=picture_dataset
decrypt_cmd="gpg --batch --decrypt --pinentry-mode loopback /root/.zfs/${zpool_name}/${zfs_dataset_name}.gpg | /usr/sbin/zfs load-key ${zpool_name}/${zfs_dataset_name}"
$ cat /etc/systemd/system/zfs-load-key@data_pool-picture_dataset.service
[Unit]
Description=Load ZFS keys
DefaultDependencies=no
Before=zfs-mount.service
After=zfs-import.target
Requires=zfs-import.target

[Service]
Type=oneshot
EnvironmentFile=/etc/zfs/service_config/%I
RemainAfterExit=yes
ExecStart=$decrypt_cmd

[Install]
WantedBy=zfs-mount.service

Btw this is not the best sample ;) but it shows what the idea behind it is

Snapshots

ComandsDescription
zfs snapshot ${pool}/${dataset}@${snapshotname}create a snapshot
zfs list -t snapshotlist all snapshots. Column “USED” is space dedicated to one snapshot. Space occupied by n snapshots only becomes visible after deleting n-1 of those snapshots.
zfs list -o space -r ${pool}list all datasets. Column “USEDSNAP” includes total size of all snapshots.
zfs destroy ${pool}/${dataset}@${snapshotname}delete a snapshot
zfs destroy ${pool}/${dataset}@${snapshotA}%${snapshotB}delete all snapshots between A and B including A and B
zfs rename ${pool}/${dataset}@${oldname} ${pool}/${dataset}@${newname}rename a snapshot
zfs rollback ${pool}/${dataset}@${snapshot}rollback
zfs clone ${pool}/${dataset}@${snapshotname} ${pool}/${newdataset}create a new dataset from a snapshot
zfs list -po written,written@${snapshotname} ${pool}/${dataset}if 0, then snapshot is pristine. add -H for usage in scripts.

Send and Receive

ComandsDescription
zfs send [pool]/[dataset]@[snapshotname] | ssh [destinationhost] (sudo) zfs receive [pool]/[dataset]send full dataset, may not exist previously on target
zfs send -Rw [pool]/[dataset]@[snapshotname] | ssh [destinationhost] (sudo) zfs receive [pool]/[dataset]same as above for encrpyted datasets
zfs send -i [pool]/[dataset]@[oldsnapshotname] [pool]/[dataset]@[newsnapshotname] | ssh [destinationhost] (sudo) zfs receive [pool]/[dataset]send incremental snapshot diff, oldsnapshot must exist on dest
zfs send -Rwi [pool]/[dataset]@[oldsnapshotname] [pool]/[dataset]@[newsnapshotname] | ssh [destinationhost] (sudo) zfs receive [pool]/[dataset]same as above for encrpyted datasets

Send and Receive with mbuffer

ssh is the more secure but a bit slower approach since it does not buffer and needs to encrypt data. In case of a trustworthy network path mbuffer can be used.
Open up a port restricted to the source IP on the destination node
mbuffer -I ${sourceIP}:${destPort} | zfs receive ${pool}/${ds}
Start transfer on source node
zfs send ${pool}/${ds}@${snapshot} | mbuffer -O ${destIP}:${destPort}
If ram allowes it, you can increase the mbuffer cache by using for example -m 4G.

Troubleshooting and Fixes

used /dev/sd instead of /dev/disk/by-id on creation

# no data loss, first export the pool
$ sudo zpool export [pool name]
# import the pool again using the right IDs
$ sudo zpool import -d /dev/disk/by-id [pool name]

replace disk of raidz1/2/3

# get status of pool
$ zpool status ${pool}
# find out which disk is the new disk in case you did not note down the serial
$ lsblk
$ ls -la /dev/disk/by-id/
# replace disk
$ zpool replace ${pool} ${old_disk_id} ${new_disk_id}