ZNAPZEND(1)

πŸ“¦ version 0.23.2, in nixpkgs unstable

znapzend β€” the ZNAPZEND(1) page, part 1 πŸ’…

okay bestie, welcome to the znapzend(1) manual page β€” yes that's the znapzend daemon, the whole icon herself. part 1 goes here, and the rest is coming right after, so don't go anywhere 🫢


✨ NAME ✨

znapzend β€” the znapzend daemon, no cap πŸ’–


🌸 SYNOPSIS 🌸

znapzend [options...]

 --man                  show man-page and exit
 --version              print version and exit
 -h,--help              display this help and exit
 -d,--debug             print debug messages to STDERR
 -n,--noaction          run in simulation mode: does no changes to filesystems
 --nodelay              if a backup plan has a zend_delay defined, ignore it
 --nodestroy            does all changes to the filesystem except destroy
 --forbidDestRollback   forbids forcing destination dataset rollback and
                        removal of datasets/snapshots not present on source
 --logto=x              select where to log (syslog::<facility> or <filepath>)
                        when logging to a file, send USR1 to re-open the filehandle
 --loglevel=x           define the log level
 --pidfile=x            write a pid file when running in daemon mode
 --daemonize            fork into the background
 --runonce=[x]          run one round on the optionally provided dataset
 -r,--recursive         recurse from the given "run-once" dataset
 --inherited            allow "run-once" on dataset which only inherits a plan
 --since=x              allow to consider a non-automatic common snapshot "x" as a starting point
 --sinceForced=x        same as --since=x but also make sure "x" exists on
                        destination (can cause removal of newer snapshots)
 --forcedSnapshotSuffix=x  use non-generated snapshot suffix for this "run-once"
 --features=x           comma separated list of features to be enabled
                        (detailed in man page):
    oracleMode recvu compressed sendRaw lowmemRecurse zfsGetType
    skipIntermediates sendIntermediates forbidDestRollback
 -i/-I                  a "zfs send" compatible on/off for skipIntermediates
 --rootExec=x           exec zfs with this command to obtain root privileges
                        (sudo or pfexec)
 --connectTimeout=x     sets the ConnectTimeout for ssh commands
 --autoCreation         automatically create dataset on destination if it does
                        not exist
 --noautoCreation       avoid automatically creating a dataset on destination
                        if it does not exist (default)
 --timeWarp=x           shift znapzend's sense of NOW into the future
                        by x seconds
 --skipOnPreSnapCmdFail skip snapshots if the pre-snap-command fails
 --skipOnPreSendCmdFail skip replication if the pre-send-command fails
 --cleanOffline         clean up source snapshots even if a destination was offline
 --mailErrorSummaryTo=rcpt  if "send task(s) failed", mail a summary to rcpt

πŸ¦‹ DESCRIPTION πŸ¦‹

so, ZnapZend is a snapshot based zfs backup daemon, and what she does is: she makes snapshots on a scheduled basis on the source filesystem and on destination filesystems. that's the whole vibe. ✨

ZnapZend reads its configuration from custom properties in the dataset. use znapzendzetup to set and list these properties β€” that's the whole tool for it, iykyk. πŸŽ€


πŸ’œ the options, one by one

  • -d, --debug β€” she talks a LOT while running. sends debug messages to stderr. it's giving yapper, but useful.
  • -n, --noaction β€” doesn't do any actions which have lasting effect. ideal to try out new configurations together with --debug. lowkey the responsible way to test, bestie.
  • --nodelay β€” if a backup plan has a zend_delay defined, ignore it. this should help when debugging the backup plan configurations, without waiting for hours to run them during experiments. literally a time saver, periodt.
  • --nodestroy β€” does all changes to the filesystem except destroying old snapshots after a successful run to snapshot and replicate to backup destinations. she does the work, she just won't clean up after herself.
  • --forbidDestRollback β€” by default znapzend uses zfs recv -F, which rolls back destination to the most recent snapshot, and in case of incremental replication streams can also destroy snapshots and other child datasets not present in the source.

the --forbidDestRollback option allows to not enforce this mode, so znapzend would instead report errors if destination is not ready to receive data without applying such operations. this can be useful for maintenance of backup destinations, but can be counter-productive for regularly scheduled operations. so like… know what you're doing πŸ™ˆ

  • --logto={syslog::facility|filepath} β€” send logs out to either syslog or a logfile. default is to send logs to syslog::daemon when running daemonized. when running in debug mode, the logs will go to STDERR by default.

when logging to a file, send USR1 to reopen the file handle after log rotation. bestie, remember the USR1. it's important.

examples:

 --logto=/var/log/znapzend.log
 --logto=syslog::daemon
  • --loglevel={debug|info|warning|err|alert} β€” define the log level when logging to file. default is debug.
  • --pidfile=path β€” write a pid file when running in daemon mode. pidfile defaults to /var/run/znapzend.pid if no pidfile is given.
  • --daemonize β€” fork into the background. main character leaves the room, but she's still working πŸ’…
  • --runonce=[dataset] β€” run one round on source dataset, or on all datasets if it's empty. this is very useful for testing. use it in connection with --noaction and --debug while testing your new configuration, or with --recursive for quick back up of whatever has backup plans in a single pool or dataset tree.
  • --forcedSnapshotSuffix=snapsuffix β€” for this round of --runonce create the specified snapshot suffix, rather than one generated by a pattern managed by 'znapzend'. note it would not get automatically cleaned per retention policy, either. this is primarily intended for manual operations where a sysadmin wants to mark the certain data state of local significance while making an unscheduled backup.

this option is not valid if the run-once mode is not requested. ⚠️

while basic sanity checks are done, it is up to the caller to specify a correct string value according to the rules of their ZFS version, and to take care that it is unique (no snapshots with such suffix already exist). so yeah, that part's on you, bestie.

see also --since=snapsuffix

  • --since=snapsuffix, --sinceForced=snapsuffix β€” enables to see a snapshot named snapsuffix in the history of the dataset under source and target pools, which should be a common snapshot present on both (or only on the source pool), and may be their newest common snapshot (that would otherwise be blocking the sync as something that needs to be removed for a full auto-creation replication and/or a usual replication).

if the target dataset does not have this named snapshot, but the otherwise discovered history of automatic snapshots allows to include it into the usual replication, it will be sent (and a newer automatically-named snapshot would be made and sent). in other cases, it is up to the systems administrator to make the original systems data consistent with that on their remote ZFS pool. no pressure or anything 😭

one could then use the --sinceForced=snapsuffix variant, which can remove from destination any snapshots that are newer than snapsuffix to make way if needed, and ensure that this snapshot appears in destination's history. by default with --since=snapsuffix such removals should not happen.

unlike the --forcedSnapshotSuffix=snapsuffix, this option does not define how the new snapshot made during this run would be called (it would be named by common timestamp-based pattern to not get in the way of subsequent regular replications).

the dataset name provided in this argument will never be removed during this run-once (even if it matches the configured znapzend timestamp-based snapshot naming pattern and is old enough to rotate away in other circumstances). iconic protected behavior honestly πŸ‘‘

example:

 znapzend --runonce=rpool/ROOT --since=20200101-01-finallyStableSetup
  • -r, --recursive β€” when backing up a particular dataset with --runonce=[dataset], don't just look at this dataset's backup plan, but iterate into its children that might have any. useful for quick backups of a pool whose root dataset has no znapzendzetup configurations defined, but some trees under it do.

see below for interaction with the --inherited option and examples. 🫢

  • --inherited β€” when backing up a particular dataset with --runonce=[dataset], don't require that the provided dataset directly has a locally defined backup plan configuration (sanity check that it is not "received" via ZFS replication). with this option in place, allow also to quickly snapshot and/or replicate datasets of subtrees which inherit a backup plan from a dataset which has it in its attributes whose source is "local".

🧚 --inherited and --recursive together 🧚

at this time, the --inherited is "sort of exclusive" vs. --recursive mode, and if both are specified the behavior depends on whether the requested dataset has some (local or inherited-from-local) backup plan. if a dataset without a backup plan is passed, the script should effectively ignore the --inherited option and just recurse until it finds some dataset(s) with proper locally defined backup plan(s). note that finding inherited plans first is highly unlikely due to ZFS attribute inheritance from parents to children. like, genuinely not how it works, but ok πŸ’€

examples below assume the following dataset layout:

+ tank (no znapzend config) | + export (local znapzend config) | | +
home (inherited znapzend config from local tank/export) | |   + user
(inherited znapzend config from local tank/export) | |     + sources
(inherited znapzend config from local tank/export) | |     +
documents (inherited znapzend config from local tank/export) | |
+ pictures (inherited znapzend config from local tank/export) | +
var (local znapzend config) + usbbackup (no znapzend config)
  + snapshots (no znapzend config)
    + export (received znapzend config)
    | + home (inherited from received usbbackup/snapshots/export)
    |   + user (inherited from received usbbackup/snapshots/export)
    |     + sources (inherited from received
usbbackup/snapshots/export)
    |     + documents (inherited from received
usbbackup/snapshots/export)
    |       + documents (inherited from received
usbbackup/snapshots/export)
    + var (received znapzend config)

examples:

  znapzend --recursive --runonce=tank

okay that's the end of this chunk β€” and there is so much more coming in the next part, so stay tuned bestie πŸ’–

This one should walk every filesystem or volume dataset defined anywhere under tank (that's the root dataset of the same-named pool) and snapshot/replicate the datasets it finds that have a backup plan configured "locally" β€” like tank/export and tank/var β€” according to configuration (so with children and beyond if the corresponding backup plan's recursive=on option is specified, iykyk). It should not waste time looking for datasets under the usbbackup pool. ✨

znapzend --runonce=tank/export/home/user/documents

So here's the situation: only tank/export "locally" defines a znapzend backup plan, which means the default znapzend behavior when you point it at a descendant dataset is to find no configuration at all (it only looks for config sourced as "local" right there in the dataset) β€” and therefore it would "run-once" nothing. πŸ’€

znapzend --recursive --runonce=tank/export/home

Same energy (no config found) when recursion starts from a dataset that has an inherited backup plan configuration β€” assuming none of its descendants have a "local" config of their own. 🌸

znapzend --inherited --runonce=tank/export/home/user/documents

Now with the --inherited option, though? It would recognize this descendant dataset as having a backup plan configuration inherited from tank/export, then look at tank/export and confirm that it has this configuration from a "local" source, and znapzend should runonce just this dataset and its descendants (so that includes tank/export/home/user/documents/pictures, but not siblings like tank/export/home/user/sources). Slay. πŸ‘‘

znapzend --inherited --recursive --runonce=tank

Since tank has no backup plan, znapzend should recurse and find the nearest datasets with configured plans β€” tank/export and tank/var β€” and process them according to configuration. πŸ¦‹

znapzend --inherited --recursive --runonce=tank/export

Since tank/export has a locally defined backup plan, znapzend should process it according to configuration. πŸŽ€

znapzend --inherited --recursive --runonce=tank/export/home

Since tank/export/home has a backup plan inherited from a locally defined one in tank/export, znapzend should process it according to configuration. πŸ’–

znapzend --recursive --runonce=usbbackup
znapzend --inherited --runonce=usbbackup/snapshots
znapzend --inherited --recursive --runonce=usbbackup/snapshots/export/home

Neither of these runs should do anything, because all datasets involved (including those found by a recursive walk) under usbbackup have neither a local definition of a backup plan, nor one inherited from a local definition. Not me double-checking. πŸ™ˆ


  • --features=feature1,feature2,... β€” enables enhanced zfs features not supported by all zfs implementations. Do not enable features unless you are sure your zfs supports (or requires) it. fr fr, vibe check your zfs first. 🧚

Available features:

  • oracleMode β€” working around the following zfs issues we have seen on oracle:
    • The multi snapshot destroy syntax is not available. So stick to destroying them individually.
    • Sometimes a snapshot can not be destroyed because of some oracle zfs bug. Only a reboot seems to be able to fix this. So we just destroy the ones we can destroy. Logging an error about the problem.
  • recvu β€” use the -u option on the receive end, to keep the destination zfs filesystems unmounted.
  • resume β€” use the -s option on the receive end, to support resumable transfer with receive_resume_token.

If you have aborted a resumable-send, you should either do a resume-send using -t <token> to continue it or clear the half-received snapshot using zfs receive -A <filesystem>. 😭 Don't leave it half-done bestie.

Example:

zfs get receive_resume_token tank/test
zfs send -t <token> | zfs receive -s tank/test
  • compressed β€” use 'compressed' to add options -Lce to the zfs send command.

Even if a source and destination datasets are both using compression, zfs send will, by default, decompress the data before sending, and zfs recv will then compress it again before writing it to disk. Using -c will skip the unnecessary decompress-compress stages. This decreases CPU load on both source and destination as well as reduces network bandwidth usage. πŸ”₯

The -L option is for large block support and -e is for embedded data support. These may require certain (Open)ZFS features to be enabled.

  • sendRaw β€” use 'sendRaw' to add option -w to zfs send commands.

For encrypted source datasets this instructs zfs not to decrypt before sending, which results in a remote backup that can't be read without the encryption key/passphrase β€” useful when the remote isn't fully trusted or not physically secure. This option must be used consistently: raw incrementals cannot be based on non-raw snapshots and vice versa. ⚠️

  • sendIntermediates β€” the opposite of skipIntermediates, detailed in the next section. This is the default mode of operation, and only included here to allow for completeness and explicitness of your configurations.
  • skipIntermediates β€” enable the 'skipIntermediates' feature to send a single increment between latest common snapshot and the newly made one. It may skip several source snaps if the destination was offline for some time, and it should skip snapshots not managed by znapzend. Normally for online destinations, the new snapshot is sent as soon as it is created on the source, so there are no automatic increments to skip.

By default 'znapzend' uses the 'zfs send -I' option on the sending end (so to include all intermediate snapshots), rather than '-i' (which would send a single big increment), to keep the destination zfs dataset history similar to the source's one. This implies the -sendIntermediates mode of operation. 🌈

Note: it was the default from beginning of 'znapzend' to make sure that in case a send operation takes too long, we still get all the intermediate snapshots sent to the destination. Beware or benefit that this can deliver not only automatic, but also manually named snapshots to your destination which would have to be cleaned manually when no longer wanted there, but that they can be used as common snapshots to repair broken syncs e.g. with --since=X options.

With the 'skipIntermediates' feature disabled, all snapshots between the latest common one and the newly created one on the source would be sent to each destination, and then the "extra" ones of those managed by znapzend may get discarded according to destination's retention policy. Note that snapshots which are NOT managed by znapzend β€” e.g. ones you created manually β€” would appear on the destination and stay there until removed manually. Also note that this may consume more disk space and transfer time than otherwise needed for the data increment. 😬

This original mode is now primarily recommended for irregular backups (on removable media or over unstable links) and cases where the sysadmin marks certain data states via snapshots as special (e.g. "before_upgrade", etc.)

The feature is recommended to be part of your 'znapzend' service setup for regular runs in stable storage/networking conditions. 🫢

  • forbidDestRollback β€” avoid use of destructive 'zfs recv -F' in favor of failing to receive; more details in description of --forbidDestRollback CLI option above.
  • lowmemRecurse β€” use 'lowmemRecurse' on systems where you have too many datasets, so a recursive listing of attributes to find backup plans exhausts the memory available to znapzend(zetup): instead, go the slower way to first list all impacted dataset names, and then query their configs one by one. 🐌
  • zfsGetType β€” use 'zfsGetType' if your 'zfs get' supports a '-t' argument for filtering by dataset type at all (e.g. one in Solaris 10 does not), AND lists properties for snapshots by default when recursing (e.g. the one in Solaris 10u8 already does), so that there is too much data to process while searching for backup plans.

If these two conditions apply to your system, the time needed for a '--recursive' search for backup plans can literally differ by hundreds of times (depending on the amount of snapshots in that dataset tree… and a decent backup plan will ensure you have a lot of those), so you would benefit from requesting this feature. ⚑

This feature should not impact the default (non-'--recursive') listings however.


  • --rootExec={sudo|pfexec} β€” execute zfs with this command, 'sudo' or 'pfexec', to obtain root privileges. This is often necessary when running znapzend as a non-privileged user with a zfs install that doesn't support finer permission controls. This also applies to the zfs commands ran on remote servers over ssh. πŸ”

For sudo, the /etc/sudoers file will need to be modified to allow for passwordless access to zfs commands if znapzend is to be ran as a daemon or the system will be used as a remote. Many ZFS installations include an /etc/sudoers.d/zfs file as an example.

  • --connectTimeout=timeout β€” sets the ssh connection timeout (in seconds). ⏱️
  • --autoCreation β€” automatically create a dataset on a destination host if it's not there yet. ✨
  • --noautoCreation β€” avoid automatically creating a dataset on a destination host if it's not there yet. This is the default behavior; the option is available to help explicitly override a setting inherited from a configuration file, etc. πŸ’…
  • --timeWarp=x β€” shift ZnapZend's sense of time into the future by x seconds. πŸ•°οΈ

The practical application of this function is to determine what will happen at some future point in time. This can be useful for testing but also when running in noaction and debug mode to determine which snapshots would be created and removed at some future point in time.

  • --skipOnPreSnapCmdFail β€” prevent snapshots of a dataset from being taken when it has a pre-snap-command defined and the command returns a non-zero exit code or is killed by a signal. πŸ›‘
  • --skipOnPreSendCmdFail β€” okay so if a dataset has a pre-snap-command defined and that command bails with a non-zero exit code (or straight up gets killed by a signal 😡), this flag stops its snapshots from being replicated to a destination. Basically: command flopped β†’ no shipping the snapshot off. Simple as that. πŸ’…
  • --cleanOffline β€” this one lets you clean snapshots of a source dataset even when one or more destination datasets straight-up failed during replication, for literally any reason (destination offline, destination full, destination pool went read-only because of storage drama, source too full to even make a snapshot, etc. 😭).

Heads up though, bestie: the most recent common snapshot for each destination (as tracked on the source, for resilience 🫢) will not be deleted from source. But this is still a potentially dangerous option β€” because if that preserved snapshot somehow gets deleted from the destination, it may require a full re-replication the next time it's online. Not a vibe. ⚠️

  • *--mailErrorSummaryTo=rcpt(@domain),...* β€” if you pass this argument, a copy of the error summary gets sent there by your system's command line mailer program. After that it's up to that program and your system setup to validate the recipient names (local or domain-suffixed) and deliver the message to some mailbox. πŸ“¬

This feature relies on a program which supports the standard sendmail-like CLI. iykyk.


🌸 EXAMPLE 🌸

Wanna test a new config? Do this:

znapzend --debug --noaction --runonce=tank/test

Wanna see what's gonna happen in one hour? Peek into the future like this:

znapzend --debug --noaction --timeWarp=3600 --runonce=tank/test

Wanna run it as a daemon? Say less:

znapzend --daemonize --pidfile=/var/run/znapzend.pid --logto=syslog::daemon

Copyright (c) 2014 by OETIKER+PARTNER AG. All rights reserved.


πŸ“œ LICENSE πŸ“œ

This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. (no cap, that's the actual legal speak πŸ’€)

You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>.


πŸ¦‹ AUTHOR πŸ¦‹

Tobias Oetiker <tobi@oetiker.ch>, Dominik Hassler <hadfl@cpan.org>


✨ HISTORY ✨

 2014-06-01 had Multi destination backup
 2014-05-30 had Initial Version

And that's the whole page, bestie β€” you ate that 🫢 This icon of a manpage is znapzend version 0.23.2, dated 2024-05-03, and honestly? It's giving legendary. Go make some snapshots, queen. πŸ’–πŸŽ€πŸŒˆ

πŸ“¦ where this page comes from