Skip site navigation (1)Skip section navigation (2)

FreeBSD Manual Pages

  
 
  

home | help
ZREPL(1)			      zrepl				ZREPL(1)

NAME
     zrepl  - zrepl Documentation GitHub license Language: Go Twitter Donate via
     Patreon Donate via GitHub Sponsors Donate via Liberapay Donate via PayPal -
     Matrix

     zrepl is a one-stop, integrated solution for ZFS replication.

GETTING STARTED
     The 10 minute quick-start guides give you a first impression.

MAIN FEATURES
     * Filesystem replication

       * [x] Pull & Push mode

       * [x] Multiple transport modes: TCP, TCP + TLS client auth, SSH

       * Advanced replication features

	 * [x] Automatic retries for temporary network errors

	 * [x] Automatic resumable send & receive

	 * [x] Automatic ZFS holds during send & receive

	 * [x] Automatic bookmark & hold management for  guaranteed  incremental
	   send & recv

	 * [x]	Encrypted raw send & receive to untrusted receivers (OpenZFS na-
	   tive encryption)

	 * [x] Properties send & receive

	 * [x] Compressed send & receive

	 * [x] Large blocks send & receive

	 * [x] Embedded data send & receive

	 * [x] Resume state send & receive

	 * [x] Bandwidth limiting

     * Automatic snapshot management

       * [x] Periodic filesystem snapshots

       * [x] Support for pre- and post-snapshot hooks with builtins for MySQL  &
	 Postgres

       * [x] Flexible pruning rule system

	 * [x] Age-based fading (grandfathering scheme)

	 * [x] Bookmarks to avoid divergence between sender and receiver

     * Sophisticated Monitoring & Logging

       * [x] Live progress reporting via zrepl status subcommand

       * [x] Comprehensive, structured logging

	 * human, logfmt and json formatting

	 * stdout, syslog and TCP (+TLS client auth) outlets

       * [x] Prometheus monitoring endpoint

     * Maintainable implementation in Go

       * [x] Cross platform

       * [x] Dynamic feature checking

       * [x] Type safe & testable code

     ATTENTION:
	zrepl  as  well as this documentation is still under active development.
	There is no stability guarantee on the	RPC  protocol  or  configuration
	format,  but  we  do  our  best  to  document  breaking  changes  in the
	Changelog.

CONTRIBUTING
     We are happy about any help we can get!

     * Financial Support

     * Explore the codebase

       * These docs live in the docs/ subdirectory

     * Document any non-obvious / confusing /  plain  broken  behavior	you  en-
       counter when setting up zrepl for the first time

     * Check  the Issues and Projects sections for things to do.  The good first
       issues and docs are suitable starting points.

	Development Workflow

	       The GitHub repository is where  all  development  happens.   Make
	       sure to read the Developer Documentation section and open new is-
	       sues or pull requests there.

TABLE OF CONTENTS
   Quick Start by Use Case
     The  goal	of  this  quick-start  guide is to give you an impression of how
     zrepl can accomodate your use case.

   Install zrepl
     Follow the OS-specific installation instructions and come back here.

   Overview Of How zrepl Works
     Check out the overview section to get a rough idea of what you are going to
     configure in the next step, then come back here.

   Configuration Examples
     zrepl   is   configured   through	 a   YAML    configuration    file    in
     /etc/zrepl/zrepl.yml.   We  have  prepared example use cases that show-case
     typical deployments and different functionality of zrepl.	We encourage you
     to read through all of the examples to get an idea of what zrepl has to of-
     fer, and how you can mix-and-match configurations for your use case.   Keep
     the full config documentation handy if a config snippet is unclear.

     Example Use Cases

   Continuous Backup of a Server
     This config example shows how we can backup our ZFS-based server to another
     machine using a zrepl push job.

     * Production server prod with filesystems to back up:

       * The entire pool zroot

       * except zroot/var/tmp and all child datasets of it

       * and  except zroot/usr/home/paranoid which belongs to a user doing back-
	 ups themselves.

     * Backup server backups with a dataset sub-tree for use by zrepl:

       * In our example, that will be storage/zrepl/sink/prod.

     Our backup solution should fulfill the following requirements:

     * Periodically snapshot the filesystems on prod every 10 minutes

     * Incrementally replicate these snapshots to  storage/zrepl/sink/prod/*  on
       backups

     * Keep only very few snapshots on prod to save disk space

     * Keep  a	fading	history (24 hourly, 30 daily, 6 monthly) of snapshots on
       backups

     * The network is untrusted - zrepl should use TLS to protect its communica-
       tion and our data.

   Analysis
     We can model this situation as two jobs:

     * A push job on prod

       * Creates the snapshots

       * Keeps a short history of local snapshots to enable incremental replica-
	 tion to backups

       * Connects to the zrepl daemon process on backups

       * Pushes snapshots backups

       * Prunes snapshots on backups after replication is complete

     * A sink job on backups

       * Accepts connections & responds to requests from prod

       * Limits   client   prod   access   to	 filesystem    sub-tree    stor-
	 age/zrepl/sink/prod

   Generate TLS Certificates
     We  use  the TLS client authentication transport to protect our data on the
     wire.  To get things going quickly, we skip setting up a  CA  and	generate
     two self-signed certificates as described here.  For convenience, we gener-
     ate the key pairs on our local machine and distribute them using ssh:

	(name=backups; openssl req -x509 -sha256 -nodes \
	 -newkey rsa:4096 \
	 -days 365 \
	 -keyout $name.key \
	 -out $name.crt -addext "subjectAltName = DNS:$name" -subj "/CN=$name")

	(name=prod; openssl req -x509 -sha256 -nodes \
	 -newkey rsa:4096 \
	 -days 365 \
	 -keyout $name.key \
	 -out $name.crt -addext "subjectAltName = DNS:$name" -subj "/CN=$name")

	ssh root@backups "mkdir /etc/zrepl"
	scp  backups.key backups.crt prod.crt root@backups:/etc/zrepl

	ssh root@prod "mkdir /etc/zrepl"
	scp  prod.key prod.crt backups.crt root@prod:/etc/zrepl

     Note that alternative transports exist, e.g. via TCP without TLS or ssh.

   Configure server prod
     We  define a push job named prod_to_backups in /etc/zrepl/zrepl.yml on host
     prod :

	jobs:
	- name: prod_to_backups
	  type: push
	  connect:
	    type: tls
	    address: "backups.example.com:8888"
	    ca: /etc/zrepl/backups.crt
	    cert: /etc/zrepl/prod.crt
	    key:  /etc/zrepl/prod.key
	    server_cn: "backups"
	  filesystems: {
	    "zroot<": true,
	    "zroot/var/tmp<": false,
	    "zroot/usr/home/paranoid": false
	  }
	  snapshotting:
	    type: periodic
	    prefix: zrepl_
	    interval: 10m
	  pruning:
	    keep_sender:
	    - type: not_replicated
	    - type: last_n
	      count: 10
	    keep_receiver:
	    - type: grid
	      grid: 1x1h(keep=all) | 24x1h | 30x1d | 6x30d
	      regex: "^zrepl_"

   Configure server backups
     We define a corresponding sink job named sink  in	/etc/zrepl/zrepl.yml  on
     host backups :

	jobs:
	- name: sink
	  type: sink
	  serve:
	      type: tls
	      listen: ":8888"
	      ca: "/etc/zrepl/prod.crt"
	      cert: "/etc/zrepl/backups.crt"
	      key: "/etc/zrepl/backups.key"
	      client_cns:
		- "prod"
	  root_fs: "storage/zrepl/sink"

   Go Back To Quickstart Guide
     Click here to go back to the quickstart guide.

   Local Snapshots + Offline Backup to an External Disk
     This  config  example shows how we can use zrepl to make periodic snapshots
     of our local workstation and back it up to a  zpool  on  an  external  disk
     which we occassionally connect.

     The local snapshots should be taken every 15 minutes for pain-free recovery
     from  CLI	disasters  (rm	-rf / and the like).  However, we do not want to
     keep the snapshots around for very long because our workstation is a little
     tight on disk space.  Thus, we only keep one hour worth of  high-resolution
     snapshots,  then  fade  them out to one per hour for a day (24 hours), then
     one per day for 14 days.

     At the end of each work day, we connect our external disk	that  serves  as
     our  workstation's  local	offline  backup.   We  want zrepl to inspect the
     filesystems and snapshots on the external pool, figure out which  snapshots
     were  created  since  the last time we connected the external disk, and use
     incremental replication to efficiently mirror our workstation to our backup
     disk.  Afterwards, we want to clean up old snapshots on the backup pool: we
     want to keep all snapshots younger than one hour, 24 for each hour  of  the
     first day, then 360 daily backups.

     A few additional requirements:

     * Snapshot  creation  and	pruning  on our workstation should happen in the
       background, without interaction from our side.

     * However, we want to explicitly trigger replication via the command line.

     * We want to use OpenZFS native encryption to protect our data on	the  ex-
       ternal  disk.   It is absolutely critical that only encrypted data leaves
       our workstation.  zrepl should provide an easy config knob for  this  and
       prevent replication of unencrypted datasets to the external disk.

     * We  want  to  be  able  to put off the backups for more than three weeks,
       i.e., longer than the lifetime of the automatically created snapshots  on
       our  workstation.   zrepl  should use bookmarks and holds to achieve this
       goal.

     * When we yank out the drive during replication and go on a long  vacation,
       we  do  not  want the partially replicated snapshot to stick around as it
       would hold on to too much disk space over time.	Therefore, we want zrepl
       to deviate from its default  behavior  and  sacrifice  resumability,  but
       nonetheless  retain the ability to do incremental replication once we re-
       turn from our vacation.	zrepl should provide an easy config knob to dis-
       able step holds for incremental replication.

     The following config snippet implements the  setup  described  above.   You
     will  likely want to customize some aspects mentioned in the top comment in
     the file.

	# This config serves as an example for a local zrepl installation that
	# backups the entire zpool `system` to `backuppool/zrepl/sink`
	#
	# The requirements covered by this setup are described in the zrepl documentation's
	# quick start section which inlines this example.
	#
	# CUSTOMIZATIONS YOU WILL LIKELY WANT TO APPLY:
	# - adjust the name of the production pool `system` in the `filesystems` filter of jobs `snapjob` and `push_to_drive`
	# - adjust the name of the backup pool `backuppool` in the `backuppool_sink` job
	# - adjust the occurences of `myhostname` to the name of the system you are backing up (cannot be easily changed once you start replicating)
	# - make sure the `zrepl_` prefix is not being used by any other zfs tools you might have installed (it likely isn't)

	jobs:

	# this job takes care of snapshot creation + pruning
	- name: snapjob
	  type: snap
	  filesystems: {
	      "system<": true,
	  }
	  # create snapshots with prefix `zrepl_` every 15 minutes
	  snapshotting:
	    type: periodic
	    interval: 15m
	    prefix: zrepl_
	  pruning:
	    keep:
	    # fade-out scheme for snapshots starting with `zrepl_`
	    # - keep all created in the last hour
	    # - then destroy snapshots such that we keep 24 each 1 hour apart
	    # - then destroy snapshots such that we keep 14 each 1 day apart
	    # - then destroy all older snapshots
	    - type: grid
	      grid: 1x1h(keep=all) | 24x1h | 14x1d
	      regex: "^zrepl_.*"
	    # keep all snapshots that don't have the `zrepl_` prefix
	    - type: regex
	      negate: true
	      regex: "^zrepl_.*"

	# This job pushes to the local sink defined in job `backuppool_sink`.
	# We trigger replication manually from the command line / udev rules using
	#  `zrepl signal wakeup push_to_drive`
	- type: push
	  name: push_to_drive
	  connect:
	    type: local
	    listener_name: backuppool_sink
	    client_identity: myhostname
	  filesystems: {
	      "system<": true
	  }
	  send:
	    encrypted: true
	  replication:
	    protection:
	      initial: guarantee_resumability
	      # Downgrade protection to guarantee_incremental which uses zfs bookmarks instead of zfs holds.
	      # Thus, when we yank out the backup drive during replication
	      # - we might not be able to resume the interrupted replication step because the partially received `to` snapshot of a `from`->`to` step may be pruned any time
	      # - but in exchange we get back the disk space allocated by `to` when we prune it
	      # - and because we still have the bookmarks created by `guarantee_incremental`, we can still do incremental replication of `from`->`to2` in the future
	      incremental: guarantee_incremental
	  snapshotting:
	    type: manual
	  pruning:
	    # no-op prune rule on sender (keep all snapshots), job `snapshot` takes care of this
	    keep_sender:
	    - type: regex
	      regex: ".*"
	    # retain
	    keep_receiver:
	    # longer retention on the backup drive, we have more space there
	    - type: grid
	      grid: 1x1h(keep=all) | 24x1h | 360x1d
	      regex: "^zrepl_.*"
	    # retain all non-zrepl snapshots on the backup drive
	    - type: regex
	      negate: true
	      regex: "^zrepl_.*"

	# This job receives from job `push_to_drive` into `backuppool/zrepl/sink/myhostname`
	- type: sink
	  name: backuppool_sink
	  root_fs: "backuppool/zrepl/sink"
	  serve:
	    type: local
	    listener_name: backuppool_sink

   Offline Backups with two (or more) External Disks
     It can be desirable to have multiple disk-based backups  of  the  same  ma-
     chine.  To accomplish this,

     * create one zpool per external HDD, each with a unique name, and

     * define  a pair of push and sink job for each of these zpools, each with a
       unique name, listener_name, and root_fs.

     The unique names ensure that the jobs don't step on each others' toes  when
     managing zrepl's ZFS abstractions .

     Click here to go back to the quickstart guide.

   Fan-out replication
     This  quick-start	example demonstrates how to implement a fan-out replica-
     tion setup where datasets on a server (A) are replicated to  multiple  tar-
     gets (B, C, etc.).

     This  example  uses  multiple  source jobs on server A and pull jobs on the
     target servers.

     WARNING:
	Before implementing this setup, please see the	caveats  listed  in  the
	fan-out replication configuration overview.

   Overview
     On the source server (A), there should be:

     * A snap job

       * Creates the snapshots

       * Handles the pruning of snapshots

     * A source job for target B

       * Accepts connections from server B and B only

     * Further source jobs for each additional target (C, D, etc.)

       * Listens on a unique port

       * Only accepts connections from the specific target

     On each target server, there should be:

     * A pull job that connects to the corresponding source job on A

       * prune_sender  should  keep all snapshots since A's snap job handles the
	 pruning

       * prune_receiver can be configured as appropriate on each target server

   Generate TLS Certificates
     Mutual TLS via the TLS client authentication transport can be used  to  se-
     cure  the	connections  between the servers. In this example, a self-signed
     certificate is created for each server without setting up a CA.

	source=a.example.com
	targets=(
	    b.example.com
	    c.example.com
	    # ...
	)

	for server in "${source}" "${targets[@]}"; do
	    openssl req -x509 -sha256 -nodes \
		-newkey rsa:4096 \
		-days 365 \
		-keyout "${server}.key" \
		-out "${server}.crt" \
		-addext "subjectAltName = DNS:${server}" \
		-subj "/CN=${server}"
	done

	# Distribute each host's keypair
	for server in "${source}" "${targets[@]}"; do
	    ssh root@"${server}" mkdir /etc/zrepl
	    scp "${server}".{crt,key} root@"${server}":/etc/zrepl/
	done

	# Distribute target certificates to the source
	scp "${targets[@]/%/.crt}" root@"${source}":/etc/zrepl/

	# Distribute source certificate to the targets
	for server in "${targets[@]}"; do
	    scp "${source}.crt" root@"${server}":/etc/zrepl/
	done

   Configure source server A
	jobs:
	# Separate job for snapshots and pruning
	- name: snapshots
	  type: snap
	  filesystems:
	    'tank<': true # all filesystems
	  snapshotting:
	    type: periodic
	    prefix: zrepl_
	    interval: 10m
	  pruning:
	    keep:
	      # Keep non-zrepl snapshots
	      - type: regex
		negate: true
		regex: '^zrepl_'
	      # Time-based snapshot retention
	      - type: grid
		grid: 1x1h(keep=all) | 24x1h | 30x1d | 12x30d
		regex: '^zrepl_'

	# Source job for target B
	- name: target_b
	  type: source
	  serve:
	    type: tls
	    listen: :8888
	    ca: /etc/zrepl/b.example.com.crt
	    cert: /etc/zrepl/a.example.com.crt
	    key: /etc/zrepl/a.example.com.key
	    client_cns:
	      - b.example.com
	  filesystems:
	    'tank<': true # all filesystems
	  # Snapshots are handled by the separate snap job
	  snapshotting:
	    type: manual

	# Source job for target C
	- name: target_c
	  type: source
	  serve:
	    type: tls
	    listen: :8889
	    ca: /etc/zrepl/c.example.com.crt
	    cert: /etc/zrepl/a.example.com.crt
	    key: /etc/zrepl/a.example.com.key
	    client_cns:
	      - c.example.com
	  filesystems:
	    'tank<': true # all filesystems
	  # Snapshots are handled by the separate snap job
	  snapshotting:
	    type: manual

	# Source jobs for remaining targets. Each one should listen on a different port
	# and reference the correct certificate and client CN.
	# - name: target_c
	#   ...

   Configure each target server
	jobs:
	# Pull from source server A
	- name: source_a
	  type: pull
	  connect:
	    type: tls
	    # Use the correct port for this specific client (eg. B is 8888, C is 8889, etc.)
	    address: a.example.com:8888
	    ca: /etc/zrepl/a.example.com.crt
	    # Use the correct key pair for this specific client
	    cert: /etc/zrepl/b.example.com.crt
	    key: /etc/zrepl/b.example.com.key
	    server_cn: a.example.com
	  root_fs: pool0/backup
	  interval: 10m
	  pruning:
	    keep_sender:
	      # Source does the pruning in its snap job
	      - type: regex
		regex: '.*'
	    # Receiver-side pruning can be configured as desired on each target server
	    keep_receiver:
	      # Keep non-zrepl snapshots
	      - type: regex
		negate: true
		regex: '^zrepl_'
	      # Time-based snapshot retention
	      - type: grid
		grid: 1x1h(keep=all) | 24x1h | 30x1d | 12x30d
		regex: '^zrepl_'

   Go Back To Quickstart Guide
     Click here to go back to the quickstart guide.

     Use zrepl configcheck to validate your configuration.  No output  indicates
     that everything is fine.

     NOTE:
	Please	open  an  issue on GitHub if your use case for zrepl is signifi-
	cantly different from those listed above.  Or even better, write  it  up
	in the same style as above and open a PR!

   Apply Configuration Changes
     We  hope  that you have found a configuration that fits your use case.  Use
     zrepl configcheck once again to make sure the config is correct (output in-
     dicates that everything is fine).	Then restart the  zrepl  daemon  on  all
     systems  involved in the replication, likely using service zrepl restart or
     systemctl restart zrepl.

     WARNING:
	Please read up carefully on the pruning rules before applying  the  con-
	fig.   In  particular, note that most example configs apply to all snap-
	shots, not just zrepl-created snapshots.  Use the following keep rule on
	sender and receiver to prevent this:

	    - type: regex
	      negate: true
	      regex: "^zrepl_.*" # <- the 'prefix' specified in snapshotting.prefix

   Watch it Work
     Run zrepl status on the active side of the  replication  setup  to  monitor
     snaphotting,  replication	and pruning activity.  To re-trigger replication
     (snapshots are separate!), use zrepl signal wakeup JOBNAME.  (refer to  the
     example  use  case document if you are uncertain which job you want to wake
     up).

     You can also use basic UNIX tools to inspect see what's going on.	 If  you
     like tmux, here is a handy script that works on FreeBSD:

	pkg install gnu-watch tmux
	tmux new -s zrepl -d
	tmux split-window -t zrepl "tail -f /var/log/messages"
	tmux split-window -t zrepl "gnu-watch 'zfs list -t snapshot -o name,creation -s creation'"
	tmux split-window -t zrepl "zrepl status"
	tmux select-layout -t zrepl tiled
	tmux attach -t zrepl

     The Linux equivalent might look like this:

	# make sure tmux is installed & let's assume you use systemd + journald
	tmux new -s zrepl -d
	tmux split-window -t zrepl  "journalctl -f -u zrepl.service"
	tmux split-window -t zrepl "watch 'zfs list -t snapshot -o name,creation -s creation'"
	tmux split-window -t zrepl "zrepl status"
	tmux select-layout -t zrepl tiled
	tmux attach -t zrepl

   What Next?
     * Read more about configuration format, options & job types

     * Configure logging & monitoring.

   Installation
     TIP:
	Note: check out the quick-start guides if you want a first impression of
	zrepl.

   Packages
     zrepl  source releases are signed & tagged by the author in the git reposi-
     tory.  Your OS vendor may provide binary  packages  of  zrepl  through  the
     package  manager.	 Additionally,	binary	releases are provided on GitHub.
     The following list may be incomplete, feel free to submit a PR with an  up-
     date:
       +----------------------+----------------------+----------------------+
       | OS / Distro	      | Install Command      | Link		    |
       +----------------------+----------------------+----------------------+
       | any		      | Statically    linked | Official GitHub	re- |
       |		      | binaries.	     | leases		    |
       +----------------------+----------------------+----------------------+
       | FreeBSD	      | pkg install zrepl    | -		    |
       |		      | 		     | https://www.fresh-   |
       |		      | 		     | ports.org/sysu-	    |
       |		      | 		     | tils/zrepl/	    |
       |		      | 		     |			    |
       |		      | 		     | FreeBSD	 Jail  With |
       |		      | 		     | iocage		    |
       +----------------------+----------------------+----------------------+
       | FreeNAS	      | 		     | FreeBSD	Jail   With |
       |		      | 		     | iocage		    |
       +----------------------+----------------------+----------------------+
       | MacOS		      | brew install zrepl   | Available    on	  - |
       |		      | 		     | homebrew 	    |
       +----------------------+----------------------+----------------------+
       | Arch Linux	      | yay install zrepl    | Available on AUR     |
       +----------------------+----------------------+----------------------+
       | Fedora  /   RHEL   / | dnf install zrepl    | RPM  repository con- |
       | OpenSUSE	      | 		     | fig		    |
       +----------------------+----------------------+----------------------+
       | Debian + Ubuntu      | apt install zrepl    | APT repository  con- |
       |		      | 		     | fig		    |
       +----------------------+----------------------+----------------------+
       | OmniOS 	      | pkg install zrepl    | Available   since  - |
       |		      | 		     | r151030		    |
       +----------------------+----------------------+----------------------+
       | Void Linux	      | xbps-install zrepl   | Available  since   - |
       |		      | 		     | a88a2a4		    |
       +----------------------+----------------------+----------------------+
       | any		      | Build from source    | README.md.	    |
       +----------------------+----------------------+----------------------+

   Debian / Ubuntu APT repositories
     We  maintain APT repositories for Debian, Ubuntu and derivatives.	The fin-
     gerprint of the signing key is E101 418F D3D6 FBCB  9D65	A62D  7086  99FC
     5F2E BF16.  It is available at https://zrepl.cschwarz.com/apt/apt-key.asc .
     Please  open  an  issue  in  on GitHub if you encounter any issues with the
     repository.

	(
	set -ex
	zrepl_apt_key_url=https://zrepl.cschwarz.com/apt/apt-key.asc
	zrepl_apt_key_dst=/usr/share/keyrings/zrepl.gpg
	zrepl_apt_repo_file=/etc/apt/sources.list.d/zrepl.list

	# Install dependencies for subsequent commands
	sudo apt update && sudo apt install curl gnupg lsb-release

	# Deploy the zrepl apt key.
	curl -fsSL "$zrepl_apt_key_url" | tee | gpg --dearmor | sudo tee "$zrepl_apt_key_dst" > /dev/null

	# Add the zrepl apt repo.
	ARCH="$(dpkg --print-architecture)"
	CODENAME="$(lsb_release -i -s | tr '[:upper:]' '[:lower:]') $(lsb_release -c -s | tr '[:upper:]' '[:lower:]')"
	echo "Using Distro and Codename: $CODENAME"
	echo "deb [arch=$ARCH signed-by=$zrepl_apt_key_dst] https://zrepl.cschwarz.com/apt/$CODENAME main" | sudo tee "$zrepl_apt_repo_file" > /dev/null

	# Update apt repos.
	sudo apt update
	)

     NOTE:
	Until zrepl reaches 1.0, the repositories will be updated to the  latest
	zrepl release immediately.  This includes breaking changes between zrepl
	versions.  Use apt-mark hold zrepl to prevent upgrades of zrepl.

   RPM repositories
     We  provide  a  single RPM repository for all RPM-based Linux distros.  The
     zrepl binary in the repo is the same as the one published to GitHub.  Since
     Go binaries are statically linked, the RPM should	work  about  everywhere.
     Please open an issue on GitHub if you encounter any issues with the reposi-
     tory.

     The  fingerprint  of the repo & package signing key is: F6F6 E8EA 6F2F 1462
     2878   B5DE   50E3   4417	 826E	2CE6.	 It   is    available	 at    -
     https://zrepl.cschwarz.com/rpm/rpm-key.asc .

     NOTE:
	Until  zrepl  reaches  1.0, the repository will be updated to the latest
	zrepl release immediately.  This includes breaking changes between zrepl
	versions.  If that bothers you, use the dnf versionlock plugin or zypper
	addlock zrepl to pin the version of zrepl on your system.

   Fedora / AlmaLinux / Rocky Linux / RHEL
     For dnf/yum-based distributions, copy-paste the following snippet:

	rpmkeys --import 'https://zrepl.cschwarz.com/rpm/rpm-key.asc'
	cat > /etc/yum.repos.d/zrepl.repo <<EOF
	[zrepl]
	name = zrepl
	baseurl = https://zrepl.cschwarz.com/rpm/repo
	gpgkey = https://zrepl.cschwarz.com/rpm/rpm-key.asc
	repo_gpgcheck = 1
	EOF
	dnf install zrepl
	# Or if you're on an older system:
	# yum install zrepl

     You will be asked by dnf to  verify  repository  metadata	(repo_gpgcheck).
     There is no way to automate that prompt.

   openSUSE
     For  SUSE-based  distributions  that  use	zypper, copy-paste the following
     snippet:

	rpmkeys --import 'https://zrepl.cschwarz.com/rpm/rpm-key.asc'
	zypper ar --check --gpgcheck-strict --refresh https://zrepl.cschwarz.com/rpm/repo zrepl
	zypper install zrepl

   User Privileges
     It is possible to run zrepl as an unprivileged user in combination with ZFS
     delegation.  Also, there is the possibility to run it in a jail on  FreeBSD
     by delegating a dataset to the jail.

     TIP:
	Note:  check out the FreeBSD Jail With iocage for FreeBSD jail setup in-
	structions.

   FreeBSD Jail With iocage
     This tutorial shows how zrepl can be installed on FreeBSD, or FreeNAS in  a
     jail  using  iocage.   While this tutorial focuses on using iocage, much of
     the setup would be similar using a different jail manager.

     NOTE:
	From a security perspective, just keep in mind that  zfs  send/recv  was
	never  designed with jails in mind, an attacker could probably crash the
	receive-side kernel or worse induce stateful damage to the  receive-side
	pool if they were able to get access to the jail.

	The jail doesn't provide security benefits, but only management ones.

   Requirements
     A	dataset  that  will  be delegated to the jail needs to be created if one
     does not already exist.  For the tutorial tank/zrepl will be used.

	zfs create -o mountpoint=none tank/zrepl

     The only software requirements on the host system are iocage, which can  be
     installed from ports or packages.

	pkg install py37-iocage

     NOTE:
	By  default  iocage  will "activate" on first use which will set up some
	defaults such as which pool will be used. To  activate	iocage	manually
	the iocage activate command can be used.

   Jail Creation
     There are two options for jail creation using FreeBSD.

     1. Manually set up the jail from scratch

     2. Create the jail using the zrepl plugin. On FreeNAS this is possible from
	the user interface using the community index.

   Manual Jail
     Create  a	jail, using the same release as the host, called zrepl that will
     be automatically started at boot.	The jail will have tank/zrepl  delegated
     into it.

	iocage create --release "$(freebsd-version -k | cut -d '-' -f '1,2')" --name zrepl \
	       boot=on nat=1 \
	       jail_zfs=on \
	       jail_zfs_dataset=zrepl \
	       jail_zfs_mountpoint='none'

     Enter the jail:

	iocage console zrepl

     Install zrepl

	pkg update && pkg upgrade
	pkg install zrepl

     Create the log file /var/log/zrepl.log

	touch /var/log/zrepl.log && service newsyslog restart

     Tell syslogd to redirect facility local0 to the zrepl.log file:

	service syslogd reload

     Enable the zrepl daemon to start automatically at boot:

	sysrc zrepl_enable="YES"

     Now jump to the summary below.

   Plugin
     When  using the plugin, zrepl will be installed for you in a jail using the
     following iocage properties.

     * nat=1

     * jail_zfs=on

     * jail_zfs_mountpoint=none

     Additionally the delegated dataset should be specified upon  creation,  and
     optionally  start	on  boot  can  be  set.   This can also be done from the
     FreeNAS webui.

	fetch https://raw.githubusercontent.com/ix-plugin-hub/iocage-plugin-index/master/zrepl.json -o /tmp/zrepl.json
	iocage fetch -P /tmp/zrepl.json --name zrepl jail_zfs_dataset=zrepl boot=on

   Configuration
     Now zrepl can be configured.

     Enter the jail.

	iocage console zrepl

     Modify the /usr/local/etc/zrepl/zrepl.yml configuration file.

     TIP:
	Note: check out the quick-start guides for examples of a sink job.

     Now zrepl can be started.

	service zrepl start

     Now jump to the summary below.

   Summary
     Congratulations, you have a working jail!

     NOTE:
	With FreeBSD 13's transition to OpenZFS 2.0,  please  ensure  that  your
	jail's FreeBSD version matches the one in the kernel module.  If you are
	getting cryptic errors such as cannot receive new filesystem stream: in-
	valid backup stream the instructions posted here might help.

   What next?
     Read the configuration chapter and then continue with the usage chapter.

     Reminder:	If  you  want  a quick introduction, please read the quick-start
     guides.

   Configuration
   Overview & Terminology
     All work zrepl does is performed by the zrepl daemon which is configured in
     a single YAML configuration file loaded on startup.   The	following  paths
     are searched, in this order:

     1. The path specified via the global --config flag

     2. /etc/zrepl/zrepl.yml

     3. /usr/local/etc/zrepl/zrepl.yml

     zrepl  configcheck  can be used to validate the configuration.  If the con-
     figuration is valid, it will output nothing and exit with code 0.	The  er-
     ror messages vary in quality and usefulness: please report confusing config
     errors to the tracking issue #155.

     Full   example   configs	are   available  at  quick-start  guides  and  -
     internal/config/samples/.	However, copy-pasting examples is no  substitute
     for reading documentation!

   Config File Structure
	global: ...
	jobs:
	- name: backup
	  type: push
	- ...

     A	zrepl  configuration file is divided in to two main sections: global and
     jobs.  global has sensible defaults. It is covered in logging, monitoring &
     miscellaneous.

   conf.d: including config files
     It is possible to distribute zrepl configurations over multiple  YAML  con-
     figuration  files. This is achieved by using the include key which can only
     exist in the main configuration file.

     The list of included paths must point to either individual  YAML  files  or
     directories.   If the path points to a directory then all YAML files with a
     .yml extention in the directory will be included.

	# /etc/zrepl/zrep.yml
	global: ...
	include:
	  - ./jobs.d/
	  - /opt/some_job.yml

	# /etc/zrepl/jobs.d/backup.yml
	jobs:
	- name: backup
	  type: push
	- ...

	# /opt/some_job.yml
	jobs:
	- name: another_job
	  ...
	- ...

     The paths are treated as absolute when starting with /, otherwise as a rel-
     ative path to the main config file's parent directory.

     Job names must be unique across all included configuration files.

   Jobs & How They Work Together
     A job is the unit of activity tracked by the zrepl daemon.  The type  of  a
     job  determines its role in a replication setup and in snapshot management.
     Jobs are identified by their name, both in log files and the  zrepl  status
     command.

     NOTE:
	The  job  name is persisted in several places on disk and thus cannot be
	changed easily.

     Replication always happens between a pair of jobs: one active side and  one
     passive  side.   The  active  side  connects  to  the  passive side using a
     transport and starts executing the replication logic.  The passive side re-
     sponds to requests from the active side after checking its permissions.

     The following table shows how  different  job  types  can	be  combined  to
     achieve  both  push  and pull mode setups.  Note that snapshot-creation de-
     noted by "(snap)" is orthogonal to whether a job is active or passive.
+------------------+---------------------------+------------------+-----------------------------------------------+
| Setup name	   | active side	       | passive side	  | use case					  |
+------------------+---------------------------+------------------+-----------------------------------------------+
| Push mode	   | push (snap)	       | sink		  |						  |
|		   |			       |		  |	 * Laptop				  |
|		   |			       |		  |	   backup				  |
|		   |			       |		  |						  |
|		   |			       |		  |	 * NAS	 be-				  |
|		   |			       |		  |	   hind  NAT				  |
|		   |			       |		  |	   to	off-				  |
|		   |			       |		  |	   site 				  |
+------------------+---------------------------+------------------+-----------------------------------------------+
| Pull mode	   | pull		       | source (snap)	  |						  |
|		   |			       |		  |	 * Central				  |
|		   |			       |		  |	   backup-server			  |
|		   |			       |		  |	   for	many				  |
|		   |			       |		  |	   nodes				  |
|		   |			       |		  |						  |
|		   |			       |		  |	 * Remote				  |
|		   |			       |		  |	   server to				  |
|		   |			       |		  |	   NAS	 be-				  |
|		   |			       |		  |	   hind NAT				  |
+------------------+---------------------------+------------------+-----------------------------------------------+
| Local   replica- | push + sink in one config |		  |						  |
| tion		   | with local transport      |      * Backup to |						  |
|		   |			       |	locally   |						  |
|		   |			       |	attached  |						  |
|		   |			       |	disk	  |						  |
|		   |			       |		  |						  |
|		   |			       |      * Backup	  |						  |
|		   |			       |	FreeBSD   |						  |
|		   |			       |	boot pool |						  |
+------------------+---------------------------+------------------+-----------------------------------------------+
| Snap		 & | snap (snap)	       | N/A		  |						  |
| prune-only	   |			       |		  |	 *					  |
|		   |			       |		  |	   Snapshots & pruning but no replication |
|		   |			       |		  |	   required				  |
|		   |			       |		  |						  |
|		   |			       |		  |						  |
|		   |			       |		  |	 * Workaround				  |
|		   |			       |		  |	   for					  |
|		   |			       |		  |	   source-side				  |
|		   |			       |		  |	   pruning				  |
+------------------+---------------------------+------------------+-----------------------------------------------+

   How the Active Side Works
     The  active  side	(push and pull job) executes the replication and pruning
     logic:

     1. Wakeup after snapshotting (push job) or pull interval ticker (pull job).

     2. Connect to the passive side and instantiate an RPC client.

     3. Replicate data from the sender to the receiver.

     4. Prune on sender & receiver.

     TIP:
	The progress of the active side can be watched live using zrepl status.

   How the Passive Side Works
     The passive side (sink and source) waits for connections  from  the  active
     side,  on the transport specified with serve in the job configuration.  The
     respective transport then perfoms authentication & authorization, resulting
     in a stable client identity.  The passive side job uses this  client  iden-
     tity as follows:

	* In  sink  jobs,  to  map  requests from different client identities to
	  their respective sub-filesystem tree root_fs/${client_identity}.

	* In  the  future,  ``source``	might  embed  the  client  identity   in
	  :ref:`zrepl's ZFS abstraction names <zrepl-zfs-abstractions>`, to sup-
	  port multi-host replication.

     TIP:
	The  use  of the client identity in the sink job implies that it must be
	usable as a ZFS ZFS filesystem name component.

   How Replication Works
     One of the major design goals of the replication module is to avoid any du-
     plication of the nontrivial logic.  As such, the  code  works  on	abstract
     senders  and receiver endpoints, where typically one will be implemented by
     a local program object and the other is an RPC client instance.  Regardless
     of push- or pull-style setup, the logic executes on the active  side,  i.e.
     in the push or pull job.

     The  following  high-level  steps	take place during replication and can be
     monitored using zrepl status:

     * Plan the replication:

       * Compare sender and receiver filesystem snapshots

       * Build the replication plan

	 * Per filesystem, compute a diff between sender and receiver snapshots

	 * Build a list of replication steps

	   * If possible, use incremental and resumable sends

	   * Otherwise, use full send of most recent snapshot on sender

       * Retry on errors that are likely temporary (i.e. network failures).

       * Give up on filesystems where a permanent error was received over RPC.

     * Execute the plan

       * Perform replication steps in the following order: Among all filesystems
	 with pending replication steps, pick the filesystem whose next replica-
	 tion step's snapshot is the oldest.

       * Create placeholder filesystems on the	receiving  side  to  mirror  the
	 dataset paths on the sender to root_fs/${client_identity}.

       * Acquire send-side step-holds on the step's from and to snapshots.

       * Perform the replication step.

       * Move the replication cursor bookmark on the sending side (see below).

       * Move the last-received-hold on the receiving side (see below).

       * Release the send-side step-holds.

     The  idea	behind	the  execution order of replication steps is that if the
     sender snapshots all filesystems simultaneously at fixed intervals, the re-
     ceiver will have all filesystems snapshotted at time T1  before  the  first
     snapshot at T2 = T1 + $interval is replicated.

   ZFS Background Knowledge
     This  section gives some background knowledge about ZFS features that zrepl
     uses to provide guarantees for  a	replication  filesystem.   Specifically,
     zrepl guarantees by default that incremental replication is always possible
     and that started replication steps can always be resumed if they are inter-
     rupted.

     ZFS Send Modes & Bookmarks ZFS supports full sends (zfs send fs@to) and in-
     cremental sends (zfs send -i @from fs@to).  Full sends are used to create a
     new  filesystem  on the receiver with the send-side state of fs@to.  Incre-
     mental sends only transfer the delta between @from  and  @to.   Incremental
     sends  require  that  @from be present on the receiving side when receiving
     the incremental stream.  Incremental sends can also use a ZFS  bookmark  as
     from  on  the sending side (zfs send -i #bm_from fs@to), where #bm_from was
     created using zfs bookmark fs@from fs#bm_from.  The receiving side must al-
     ways have the actual snapshot @from, regardless of whether the sending side
     uses @from or a bookmark of it.

     Plain and raw sends By default, zfs send  sends  the  most  generic,  back-
     wards-compatible  data stream format (so-called 'plain send').  If the sent
     uses newer features, e.g. compression or encryption, zfs send has to  un-do
     these  operations	on the fly to produce the plain send stream.  If the re-
     ceiver uses newer features (e.g. compression or encryption  inherited  from
     the  parent  FS), it applies the necessary transformations again on the fly
     during zfs recv.

     Flags such as -e, -c and -L  tell ZFS to produce  a  send	stream	that  is
     closer to how the data is stored on disk.	Sending with those flags removes
     computational  overhead  from  sender  and receiver.  However, the receiver
     will not apply certain transformations, e.g., it will not compress with the
     receive-side compression algorithm.

     The -w (--raw) flag produces a send stream that is as raw as possible.  For
     unencrypted datasets, its current effect is the same as -Lce.

     Encrypted datasets can only be sent plain (unencrypted) or raw  (encrypted)
     using the -w flag.

     Resumable	Send  & Recv The -s flag for zfs recv tells zfs to save the par-
     tially received send stream in case  it  is  interrupted.	 To  resume  the
     replication,  the	receiving side filesystem's receive_resume_token must be
     passed to a new zfs send -t <value> | zfs recv command.  A  full  send  can
     only  be  resumed if @to still exists.  An incremental send can only be re-
     sumed if @to still exists and either @from still exists or a bookmark  #fbm
     of @from still exists.

     ZFS  Holds  ZFS holds prevent a snapshot from being deleted through zfs de-
     stroy, letting the destroy fail with a datset is  busy  error.   Holds  are
     created  and  referred to by a tag. They can be thought of as a named, per-
     sistent lock on the snapshot.

   ZFS Abstractions Managed By zrepl
     With the background knowledge from the previous paragraph, we now summarize
     the different on-disk ZFS objects that zrepl manages to provide  its  func-
     tionality.

     Placeholder  filesystems  on the receiving side are regular ZFS filesystems
     with the ZFS property zrepl:placeholder=on.  Placeholders allow the receiv-
     ing side to mirror the sender's ZFS dataset hierarchy  without  replicating
     every  filesystem	at  every intermediary dataset path component.	Consider
     the following example: S/H/J shall be replicated to  R/sink/job/S/H/J,  but
     neither  S/H  nor	S  shall  be  replicated.  ZFS requires the existence of
     R/sink/job/S and R/sink/job/S/H in order to receive into  R/sink/job/S/H/J.
     Thus, zrepl creates the parent filesystems as placeholders on the receiving
     side.   If  at some point S/H and S shall be replicated, the receiving side
     invalidates the placeholder flag automatically.  The zrepl test placeholder
     command can be used to check whether a filesystem is a placeholder.

     The replication cursor bookmark and last-received-hold are managed by zrepl
     to ensure that future replications can always be done  incrementally.   The
     replication  cursor is a send-side bookmark of the most recent successfully
     replicated snapshot, and the last-received-hold is a hold of that	snapshot
     on  the receiving side.  Both are moved atomically after the receiving side
     has confirmed that a replication step is complete.

     The replication cursor has  the  format  #zrepl_CUSOR_G_<GUID>_J_<JOBNAME>.
     The  last-received-hold tag has the format zrepl_last_received_J_<JOBNAME>.
     Encoding the job name in the names ensures that multiple sending  jobs  can
     replicate the same filesystem to different receivers without interference.

     Tentative	replication cursor bookmarks are short-lived bookmarks that pro-
     tect the atomic moving-forward  of  the  replication  cursor  and	last-re-
     ceived-hold  (see	this  issue).  They are only necessary if step holds are
     not used as per the replication.protection setting.  The tentative replica-
     tion cursor has the format #zrepl_CUSORTENTATIVE_G_<GUID>_J_<JOBNAME>.  The
     zrepl zfs-abstraction list command provides a listing of all bookmarks  and
     holds managed by zrepl.

     Step holds are zfs holds managed by zrepl to ensure that a replication step
     can  always  be  resumed if it is interrupted, e.g., due to network outage.
     zrepl creates step holds before it attempts a replication step and releases
     them after the receiver confirms that the	replication  step  is  complete.
     For  an  initial  replication  full @initial_snap, zrepl puts a zfs hold on
     @initial_snap.  For an incremental send @from -> @to, zrepl puts a zfs hold
     on both @from and @to.  Note that @from is not strictly necessary	for  re-
     sumability  --  a	bookmark on the sending side would be sufficient --, but
     size-estimation in currently used OpenZFS versions only works if @from is a
     snapshot.	The hold tag has the format zrepl_STEP_J_<JOBNAME>.  A job  only
     ever  has	one active send per filesystem.  Thus, there are never more than
     two step holds for a given pair of (job,filesystem).

     Step bookmarks are zrepl's equivalent for holds on bookmarks (ZFS does  not
     support  putting  holds  on  bookmarks).  They are intended for a situation
     where a replication step uses a bookmark #bm as incremental from where  #bm
     is  not managed by zrepl.	To ensure resumability, zrepl copies #bm to step
     bookmark #zrepl_STEP_G_<GUID>_J_<JOBNAME>.  If the  replication  is  inter-
     rupted  and #bm is deleted by the user, the step bookmark remains as an in-
     cremental source for the resumable send.  Note that zrepl does not yet sup-
     port creating step bookmarks because  the	corresponding  ZFS  feature  for
     copying  bookmarks  is not yet widely available .	Subscribe to zrepl issue
     #326 for details.

     The zrepl zfs-abstraction list command provides a listing of all  bookmarks
     and holds managed by zrepl.

     NOTE:
	More details can be found in the design document replication/design.md.

   Caveats With Complex Setups (More Than 2 Jobs or Machines)
     Most  users are served well with a single sender and a single receiver job.
     This section documents considerations for more complex setups.

     ATTENTION:
	Before you continue, make sure you have a working understanding  of  how
	zrepl  works  and  what  zrepl	does  to ensure that replication between
	sender and receiver is always possible	without  conflicts.   This  will
	help  you  understand  why  certain kinds of multi-machine setups do not
	(yet) work.

     NOTE:
	If you can't find your desired configuration, have  questions  or  would
	like  to  see  improvements to multi-job setups, please open an issue on
	GitHub.

   Multiple Jobs on One Machine
     As a general rule, multiple jobs configured on one machine must operate  on
     disjoint  sets  of filesystems.  Otherwise, concurrently running jobs might
     interfere when operating on the same filesystem.

     On your setup, ensure that

     * all filesystems filter specifications are disjoint

     * no root_fs is a prefix or equal to another root_fs

       * For sink jobs, consider all possible root_fs/${client_identity}.

     * no filesystems filter matches any root_fs

     Exceptions to the rule:

     * A snap and push job on the same machine can match the  same  filesystems.
       To  avoid  interference, only one of the jobs should be pruning snapshots
       on the sender, the other one should keep all snapshots.	Since  the  jobs
       won't  coordinate,  errors in the log are to be expected, but zrepl's ZFS
       abstractions ensure that push and sink  can  always  replicate  incremen-
       tally.  This scenario is detailed in one of the quick-start guides.

   Two Or More Machines
     This  section  might  be  relevant  to users who wish to fan-in (N machines
     replicate to 1) or fan-out (replicate 1 machine to N machines).

     Working setups:

     * Fan-in: N servers replicated to one receiver, disjoint dataset trees.

       * This is the common use case of a centralized backup server.

       * Implementation:

	 * N push jobs (one per sender server), 1 sink (as long as the different
	   push jobs have a different client identity)

	 * N source jobs (one per sender server), N pull on the receiver  server
	   (unique names, disjoint  root_fs)

       * The  sink  job  automatically	constrains  each  client  to  a disjoint
	 sub-tree of the sink-side dataset  hierarchy  ${root_fs}/${client_iden-
	 tity}.  Therefore, the different clients cannot interfere.

       * The  pull job only pulls from one host, so it's up to the zrepl user to
	 ensure that the different pull jobs don't interfere.

     * Fan-out: 1 server replicated to N receivers

       * Can be implemented either in a pull or push fashion.

	 * pull setup: 1 pull job on each receiver server, each  with  a  corre-
	   sponding unique source job on the sender server.

	 * push  setup:  1  sink job on each receiver server, each with a corre-
	   sponding unique push job on the sender server.

       * It is critical that we have one sending-side job (source, push) per re-
	 ceiver.  The reason is that zrepl's  ZFS  abstractions  (zrepl  zfs-ab-
	 straction  list)  include  the name of the source/push job, but not the
	 receive-side job name or  client  identity  (see  issue  #380).   As  a
	 counter-example,  suppose  we	used  multiple	pull  jobs with only one
	 source job.  All pull jobs would  share  the  same  replication  cursor
	 bookmark  and	trip  over  each other, breaking incremental replication
	 guarantees quickly.  The anlogous problem exists for 1 push to  N  sink
	 jobs.

       * The  filesystems  matched  by the sending side jobs (source, push) need
	 not necessarily be disjoint.  For this to work, we need to avoid inter-
	 ference between snapshotting and pruning of the different sending jobs.
	 The solution is to centralize sender-side snapshot management in a sep-
	 arate snap job.  Snapshotting in the source/push  job	should	then  be
	 disabled  (type:  manual).  And sender-side pruning (keep_sender) needs
	 to be disabled in the active side (pull / push), since that'll be  done
	 by the snap job.

       * Restore limitations: when restoring from one of the pull targets (e.g.,
	 using zfs send -R), the replication cursor bookmarks don't exist on the
	 restored  system.   This can break incremental replication to all other
	 receive-sides after restore.

       * See the fan-out replication quick-start guide for an  example	of  this
	 setup.

     Setups that do not work:

     * N pull identities, 1 source job. Tracking issue #380.

   Job Types in Detail
   Job Type push
	       +---------------------+-----------------------------+
	       | Parameter	     | Comment			   |
	       +---------------------+-----------------------------+
	       | type		     | = push			   |
	       +---------------------+-----------------------------+
	       | name		     | unique  name  of  the job - |
	       |		     | (must not change)	   |
	       +---------------------+-----------------------------+
	       | connect	     | connect specification	   |
	       +---------------------+-----------------------------+
	       | filesystems	     | filter  specification   for |
	       |		     | filesystems to be snapshot- |
	       |		     | ted and pushed to the sink  |
	       +---------------------+-----------------------------+
	       | send		     | send  options, e.g. for en- |
	       |		     | crypted sends		   |
	       +---------------------+-----------------------------+
	       | snapshotting	     | snapshotting specification  |
	       +---------------------+-----------------------------+
	       | pruning	     | pruning specification	   |
	       +---------------------+-----------------------------+
	       | replication	     | replication options	   |
	       +---------------------+-----------------------------+
	       | conflict_resolution | conflict resolution options |
	       +---------------------+-----------------------------+

     Example config: internal/config/samples/push.yml

   Job Type sink
		    +-----------+-----------------------------+
		    | Parameter | Comment		      |
		    +-----------+-----------------------------+
		    | type	| = sink		      |
		    +-----------+-----------------------------+
		    | name	| unique name of  the  job  - |
		    |		| (must not change)	      |
		    +-----------+-----------------------------+
		    | serve	| serve specification	      |
		    +-----------+-----------------------------+
		    | root_fs	| ZFS	filesystems  are  re- |
		    |		| ceived		   to |
		    |		| $root_fs/$client_iden-      |
		    |		| tity/$source_path	      |
		    +-----------+-----------------------------+

     Example config: internal/config/samples/sink.yml

   Job Type pull
+---------------------+----------------------------------------------------------------------------+
| Parameter	      | Comment 								   |
+---------------------+----------------------------------------------------------------------------+
| type		      | = pull									   |
+---------------------+----------------------------------------------------------------------------+
| name		      | unique	name  of  the job -						   |
|		      | (must not change)							   |
+---------------------+----------------------------------------------------------------------------+
| connect	      | connect specification							   |
+---------------------+----------------------------------------------------------------------------+
| root_fs	      | ZFS  filesystems  are	re-						   |
|		      | ceived			 to						   |
|		      | $root_fs/$source_path							   |
+---------------------+----------------------------------------------------------------------------+
| interval	      | Interval at which to pull from the source job (e.g. 10m).		   |
|		      | manual disables periodic pulling, replication then only happens on wakeup. |
+---------------------+----------------------------------------------------------------------------+
| pruning	      | pruning specification							   |
+---------------------+----------------------------------------------------------------------------+
| replication	      | replication options							   |
+---------------------+----------------------------------------------------------------------------+
| conflict_resolution | conflict resolution options						   |
+---------------------+----------------------------------------------------------------------------+

     Example config: internal/config/samples/pull.yml

   Job Type source
		   +--------------+-----------------------------+
		   | Parameter	  | Comment			|
		   +--------------+-----------------------------+
		   | type	  | = source			|
		   +--------------+-----------------------------+
		   | name	  | unique name of  the  job  - |
		   |		  | (must not change)		|
		   +--------------+-----------------------------+
		   | serve	  | serve specification 	|
		   +--------------+-----------------------------+
		   | filesystems  | filter   specification  for |
		   |		  | filesystems to be snapshot- |
		   |		  | ted and exposed to connect- |
		   |		  | ing clients 		|
		   +--------------+-----------------------------+
		   | send	  | send options, e.g. for  en- |
		   |		  | crypted sends		|
		   +--------------+-----------------------------+
		   | snapshotting | snapshotting specification	|
		   +--------------+-----------------------------+

     Example config: internal/config/samples/source.yml

   Local replication
     If  you  have the need for local replication (most likely between two local
     storage pools), you can use the local transport type  to  connect	a  local
     push job to a local sink job.

     Example config: internal/config/samples/local.yml.

   Job Type snap (snapshot & prune only)
     Job  type	that  only takes snapshots and performs pruning on the local ma-
     chine.
		   +--------------+-----------------------------+
		   | Parameter	  | Comment			|
		   +--------------+-----------------------------+
		   | type	  | = snap			|
		   +--------------+-----------------------------+
		   | name	  | unique name of  the  job  - |
		   |		  | (must not change)		|
		   +--------------+-----------------------------+
		   | filesystems  | filter   specification  for |
		   |		  | filesystems to be snapshot- |
		   |		  | ted 			|
		   +--------------+-----------------------------+
		   | snapshotting | snapshotting specification	|
		   +--------------+-----------------------------+
		   | pruning	  | pruning specification	|
		   +--------------+-----------------------------+

     Example config: internal/config/samples/snap.yml

   Transports
     The zrepl RPC layer uses transports to establish  a  single,  bidirectional
     data  stream  between  an active and passive job.	On the passive (serving)
     side, the transport also provides the client identity to the upper  layers:
     this  string  is  used  for  access  control  and	separation of filesystem
     sub-trees in sink jobs.  Transports are specified in the connect  or  serve
     section of a job definition.

   Contents
     * Transports

       * tcp Transport

	 * Serve

	 * Connect

       * tls Transport

	 * Serve

	 * Connect

	 * Mutual-TLS between Two Machines

	 * Certificate Authority using EasyRSA

       * ssh+stdinserver Transport

	 * Serve

	 * Connect

       * local Transport

     ATTENTION:
	The  client identities must be valid ZFS dataset path components because
	the  sink  job	uses  ${root_fs}/${client_identity}  to  determine   the
	client's subtree.

   tcp Transport
     The  tcp  transport  uses	plain  TCP, which means that the data is not en-
     crypted on the wire.  Clients are identified by  their  IPv4  or  IPv6  ad-
     dresses,  and  the  client identity is established through a mapping on the
     server.

     This transport may also be used in conjunction with  network-layer  encryp-
     tion  and/or  VPN	tunnels  to provide encryption on the wire.  To make the
     IP-based client authentication effective, such solutions should provide au-
     thenticated IP addresses.	Some options to consider:

     * WireGuard: Linux-focussed, in-kernel TLS

     * OpenVPN: Cross-platform VPN, uses tun on *nix

     * IPSec: Properly standardized, in-kernel network-layer VPN

     * spiped: think of it as an encrypted pipe between two servers

     * SSH

       * sshuttle: VPN-like solution, but using SSH

       * SSH port forwarding: Systemd user unit & make it start before the zrepl
	 service.

   Serve
	jobs:
	- type: sink
	  serve:
	    type: tcp
	    listen: ":8888"
	    listen_freebind: true # optional, default false
	    clients: {
	      "192.168.122.123" :		"mysql01",
	      "192.168.122.42" :		"mx01",
	      "2001:0db8:85a3::8a2e:0370:7334": "gateway",

	      # CIDR masks require a '*' in the client identity string
	      # that is expanded to the client's IP address

	      "10.23.42.0/24":	     "cluster-*"
	      "fde4:8dba:82e1::/64": "san-*"
	    }
	  ...

     listen_freebind controls whether the socket is allowed to bind to non-local
     or unconfigured IP addresses (Linux IP_FREEBIND , FreeBSD IP_BINDANY).  En-
     able this option if you want to listen on a specific IP address that  might
     not yet be configured when the zrepl daemon starts.

   Connect
	jobs:
	 - type: push
	   connect:
	     type: tcp
	     address: "10.23.42.23:8888"
	     dial_timeout: # optional, default 10s
	   ...

   tls Transport
     The  tls  transport  uses TCP + TLS with client authentication using client
     certificates.  The client identity is the common name (CN) presented in the
     client certificate.

     It is recommended to set up a dedicated CA infrastructure for  this  trans-
     port,  e.g.  using OpenVPN's EasyRSA.  For a simple 2-machine setup, mutual
     TLS might also be sufficient.  We	provide  copy-pastable	instructions  to
     generate the certificates below.

     The implementation uses Go's TLS library.	Since Go binaries are statically
     linked,  you or your distribution need to recompile zrepl when vulnerabili-
     ties in that library are disclosed.

     All file paths are resolved relative to the zrepl daemon's  working  direc-
     tory.   Specify absolute paths if you are unsure what directory that is (or
     find out from your init system).

     If intermediate CAs are used, the full chain must be present in  either  in
     the  ca  file  or the individual cert files.  Regardless, the client's cer-
     tificate must be first in the cert file, with  each  following  certificate
     directly  certifying  the one preceding it (see TLS's specification).  This
     is the common default when using a CA management tool.

     NOTE:
	As of Go 1.15 (zrepl 0.3.0 and newer), the Go TLS  /  x509  library  re-
	qurires  Subject Alternative Names be present in certificates. You might
	need to re-generate your certificates using one of the two  alternatives
	provided below.

	Note  further that zrepl continues to use the CommonName field to assign
	client identities.  Hence, we recommend to keep the Subject  Alternative
	Name and the CommonName in sync.

   Serve
	jobs:
	  - type: sink
	    root_fs: "pool2/backup_laptops"
	    serve:
	      type: tls
	      listen: ":8888"
	      listen_freebind: true # optional, default false
	      ca:   /etc/zrepl/ca.crt
	      cert: /etc/zrepl/prod.fullchain
	      key:  /etc/zrepl/prod.key
	      client_cns:
		- "laptop1"
		- "homeserver"

     The  ca  field  specified the certificate authority used to validate client
     certificates.  The client_cns list specifies a list of accepted client com-
     mon names (which are also the client identities for this  transport).   The
     listen_freebind field is explained here.

   Connect
	jobs:
	- type: pull
	  connect:
	    type: tls
	    address: "server1.foo.bar:8888"
	    ca:   /etc/zrepl/ca.crt
	    cert: /etc/zrepl/backupserver.fullchain
	    key:  /etc/zrepl/backupserver.key
	    server_cn: "server1"
	    dial_timeout: # optional, default 10s

     The  ca  field  specifies	the  CA  which	signed	the server's certificate
     (serve.cert).  The server_cn specifies the expected common name (CN) of the
     server's certificate.  It overrides the hostname specified in address.  The
     connection fails if either do not match.

   Mutual-TLS between Two Machines
     For a two-machine setup,  self-signed  certificates  distributed  using  an
     out-of-band mechanism will also work just fine:

     Suppose  you  have  a push-mode setup, with backups.example.com running the
     sink job, and prod.example.com running the push  job.   Run  the  following
     OpenSSL  commands on each host, substituting HOSTNAME in both filenames and
     the interactive input prompt by OpenSSL:

	(name=HOSTNAME; openssl req -x509 -sha256 -nodes \
	 -newkey rsa:4096 \
	 -days 365 \
	 -keyout $name.key \
	 -out $name.crt -addext "subjectAltName = DNS:$name" -subj "/CN=$name")

     Now   copy   each	 machine's   HOSTNAME.crt   to	 the   other   machine's
     /etc/zrepl/HOSTNAME.crt,  for  example using scp.	The serve & connect con-
     figuration will thus look like the following:

	# on backups.example.com
	- type: sink
	  serve:
	    type: tls
	    listen: ":8888"
	    ca: "/etc/zrepl/prod.example.com.crt"
	    cert: "/etc/zrepl/backups.example.com.crt"
	    key: "/etc/zrepl/backups.example.com.key"
	    client_cns:
	      - "prod.example.com"
	  ...

	# on prod.example.com
	- type: push
	  connect:
	    type: tls
	    address:"backups.example.com:8888"
	    ca: /etc/zrepl/backups.example.com.crt
	    cert: /etc/zrepl/prod.example.com.crt
	    key:  /etc/zrepl/prod.example.com.key
	    server_cn: "backups.example.com"
	  ...

   Certificate Authority using EasyRSA
     For more than two machines, it might make sense to set up a CA  infrastruc-
     ture.  Tools like EasyRSA make this very easy:

	#!/usr/bin/env bash
	set -euo pipefail

	HOSTS=(backupserver prod1 prod2 prod3 10.23.42.1)

	curl -L https://github.com/OpenVPN/easy-rsa/releases/download/v3.2.5/EasyRSA-3.2.5.tgz > EasyRSA-3.2.5.tgz
	echo "662ee3b453155aeb1dff7096ec052cd83176c460cfa82ac130ef8568ec4df490	EasyRSA-3.2.5.tgz" | sha256sum -c
	rm -rf EasyRSA-3.2.5
	tar -xf EasyRSA-3.2.5.tgz
	cd EasyRSA-3.2.5
	./easyrsa --batch
	./easyrsa --batch init-pki
	./easyrsa --batch build-ca nopass

	for host in "${HOSTS[@]}"; do
	    ./easyrsa --batch --auto-san build-serverClient-full $host nopass
	    echo cert for host $host available at pki/issued/$host.crt
	    echo key for host $host available at pki/private/$host.key
	done
	echo ca cert available at pki/ca.crt

   ssh+stdinserver Transport
     ssh+stdinserver  uses  the ssh command and some features of the server-side
     SSH authorized_keys file.	It is less efficient than other  transports  be-
     cause the data passes through two more pipes.  However, it is fairly conve-
     nient  to	set up and allows the zrepl daemon to not be directly exposed to
     the internet, because all traffic passes through the system's SSH server.

     The concept is inspired by git shell and Borg Backup.   The  implementation
     is provided by the Go package github.com/problame/go-netssh.

     NOTE:
	ssh+stdinserver generally provides inferior error detection and handling
	compared  to  the  tcp and tls transports.  When encountering such prob-
	lems, consider using  tcp or tls transports,  or  help	improve  package
	go-netssh.

   Serve
	jobs:
	- type: source
	  serve:
	    type: stdinserver
	    client_identities:
	    - "client1"
	    - "client2"
	  ...

     First of all, note that type=stdinserver in this case: Currently, only con-
     nect.type=ssh+stdinserver	can  connect to a serve.type=stdinserver, but we
     want to keep that option open for future extensions.

     The serving job opens a UNIX socket named after client_identity in the run-
     time directory.   In  our	example  above,  that  is  /var/run/zrepl/stdin-
     server/client1 and /var/run/zrepl/stdinserver/client2.

     On  the  same  machine, the zrepl stdinserver $client_identity command con-
     nects to /var/run/zrepl/stdinserver/$client_identity.  It then  passes  its
     stdin  and  stdout file descriptors to the zrepl daemon via cmsg(3).  zrepl
     daemon in turn combines  them  into  an  object  implementing  net.Conn:  a
     Write()  turns  into  a  write  to  stdout, a Read() turns into a read from
     stdin.

     Interactive use of the stdinserver subcommand does  not  make  much  sense.
     However,  we can force its execution when a user with a particular SSH pub-
     key connects via SSH.  This can be achieved with an  entry  in  the  autho-
     rized_keys file of the serving zrepl daemon.

	# for OpenSSH >= 7.2
	command="zrepl stdinserver CLIENT_IDENTITY",restrict CLIENT_SSH_KEY
	# for older OpenSSH versions
	command="zrepl stdinserver CLIENT_IDENTITY",no-port-forwarding,no-X11-forwarding,no-pty,no-agent-forwarding,no-user-rc CLIENT_SSH_KEY

     * CLIENT_IDENTITY	is  substituted  with an entry from client_identities in
       our example

     * CLIENT_SSH_KEY is substituted with the public part  of  the  SSH  keypair
       specified in the connect.identity_file directive on the connecting host.

     NOTE:
	You  may need to adjust the PermitRootLogin option in /etc/ssh/sshd_con-
	fig to forced-commands-only or	higher	for  this  to  work.   Refer  to
	sshd_config(5) for details.

     To  recap,  this  is of how client authentication works with the ssh+stdin-
     server transport:

     * Connections  to	the  /var/run/zrepl/stdinserver/${client_identity}  UNIX
       socket  are blindly trusted by zrepl daemon.  The connection client iden-
       tity is the name of the socket, i.e. ${client_identity}.

     * Thus, the runtime directory must be private to the zrepl  user  (this  is
       checked by zrepl daemon)

     * The  admin  of the host with the serving zrepl daemon controls the autho-
       rized_keys file.

     * Thus, the administrator controls the mapping PUBKEY -> CLIENT_IDENTITY.

   Connect
	jobs:
	- type: pull
	  connect:
	    type: ssh+stdinserver
	    host: prod.example.com
	    user: root
	    port: 22
	    identity_file: /etc/zrepl/ssh/identity
	    # options: # optional, default [], `-o` arguments passed to ssh
	    # - "Compression=yes"
	    # dial_timeout: 10s # optional, default 10s, max time.Duration until initial handshake is completed

     The connecting zrepl daemon

     1. Creates a pipe

     2. Forks

     3. In the forked process

	1. Replaces forked stdin and stdout with the corresponding pipe ends

	2. Executes the ssh binary found in $PATH.

	   1. The identity file (-i) is set to $identity_file.

	   2. The remote user, host and port correspond to those configured.

	   3. Further options can be specified using the  options  field,  which
	      appends  each  entry in the list to the command line using -o $en-
	      try.

     4. Wraps the pipe ends in a net.Conn and returns it to the RPC layer.

     As discussed in the section above, the connecting zrepl daemon expects that
     zrepl stdinserver $client_identity is  executed automatically via an autho-
     rized_keys file entry.

     The known_hosts file used by the ssh command must contain an entry for con-
     nect.host prior to starting zrepl.  Thus, run the following on the  pulling
     host's command line (substituting connect.host):

	ssh -i /etc/zrepl/ssh/identity root@prod.example.com

     NOTE:
	The  environment  variables  of  the underlying SSH process are cleared.
	$SSH_AUTH_SOCK will not be available.  It is suggested to create a sepa-
	rate, unencrypted SSH key solely for that purpose.

   local Transport
     The local transport can be used to implement local replication, i.e.,  push
     replication  between  a push and sink job defined in the same configuration
     file.

     The listener_name is analogous to a hostname and must match  between  serve
     and connect.  The client_identity is used by the sink as documented above.

	jobs:
	- type: sink
	  serve:
	    type: local
	    listener_name: localsink
	  ...

	- type: push
	  connect:
	    type: local
	    listener_name: localsink
	    client_identity: local_backup
	    dial_timeout: 2s # optional, 0 for no timeout
	  ...

   Filter Syntax
     For  source, push and snap jobs, a filesystem filter must be defined (field
     filesystems).  A filter takes a filesystem path (in the ZFS filesystem  hi-
     erarchy) as parameter and returns true (pass) or false (block).

     A	filter	is  specified  as  a  YAML  dictionary with patterns as keys and
     booleans as values.  The following rules determine which result  is  chosen
     for a given filesystem path:

     * More specific path patterns win over less specific ones

     * Non-wildcard  patterns (full path patterns) win over subtree wildcards (<
       at end of pattern)

     * If the path in question does not match any pattern, the result is false.

     The subtree wildcard < means "the dataset left of < and all its children".

     TIP:
	You can try out patterns for a	configured  job  using	the  zrepl  test
	filesystems subcommand for push and source jobs.

   Examples
   Full Access
     The following configuration will allow access to all filesystems.

	jobs:
	- type: source
	  filesystems: {
	    "<": true,
	  }
	  ...

   Fine-grained
     The following configuration demonstrates all rules presented above.

	jobs:
	- type: source
	  filesystems: {
	    "tank<": true,	    # rule 1
	    "tank/foo<": false,     # rule 2
	    "tank/foo/bar": true,  # rule 3
	  }
	  ...

     Which rule applies to given path, and what is the result?

	tank/foo/bar/loo => 2	 false
	tank/bar	 => 1	 true
	tank/foo/bar	 => 3	 true
	zroot		 => NONE false
	tank/var/log	 => 1	 true

   Send & Recv Options
   Send Options
     Source and push jobs have an optional send configuration section.

	jobs:
	- type: push
	  filesystems: ...
	  send:
	    # flags from the table below go here
	  ...

     The following table specifies the list of (boolean) options.  Flags with an
     entry in the zfs send column map directly to the zfs send CLI flags.  zrepl
     does not perform feature checks for these flags.  If you enable a flag that
     is  not  supported by the installed version of ZFS, the zfs error will show
     up at runtime in the logs and zrepl status.  See the upstream man page (man
     zfs-send) for their semantics.
	      +-------------------+----------+----------------------+
	      | send.		  | zfs send | Comment		    |
	      +-------------------+----------+----------------------+
	      | encrypted	  |	     | Specific  to  zrepl, |
	      | 		  |	     | see below.	    |
	      +-------------------+----------+----------------------+
	      | bandwidth_limit   |	     | Specific  to  zrepl, |
	      | 		  |	     | see below.	    |
	      +-------------------+----------+----------------------+
	      | raw		  | -w	     | Use   encrypted	 to |
	      | 		  |	     | only allow encrypted |
	      | 		  |	     | sends.  Mixed  sends |
	      | 		  |	     | are not supported.   |
	      +-------------------+----------+----------------------+
	      | send_properties   | -p	     | Be careful, read the |
	      | 		  |	     | note   on   property |
	      | 		  |	     | replication below.   |
	      +-------------------+----------+----------------------+
	      | backup_properties | -b	     | Be careful, read the |
	      | 		  |	     | note   on   property |
	      | 		  |	     | replication below.   |
	      +-------------------+----------+----------------------+
	      | large_blocks	  | -L	     | Potential data  loss |
	      | 		  |	     | on  OpenZFS  <  2.0, |
	      | 		  |	     | see warning below.   |
	      +-------------------+----------+----------------------+
	      | compressed	  | -c	     |			    |
	      +-------------------+----------+----------------------+
	      | embedded_data	  | -e	     |			    |
	      +-------------------+----------+----------------------+
	      | saved		  | -S	     |			    |
	      +-------------------+----------+----------------------+

   encrypted
     The encrypted option controls whether the matched filesystems are sent as -
     OpenZFS native encryption raw sends.  More specifically, if encrypted=true,
     zrepl

     * checks for any of the filesystems matched by filesystems whether the  ZFS
       encryption  property  indicates that the filesystem is actually encrypted
       with ZFS native encryption and

     * invokes the zfs send subcommand with the -w option (raw sends) and

     * expects the receiving side to support  OpenZFS  native  encryption  (recv
       will fail otherwise)

     Filesystems  matched by filesystems that are not encrypted are not sent and
     will cause error log messages.

     If encrypted=false, zrepl expects that filesystems matching filesystems are
     not encrypted or have loaded encryption keys.

     NOTE:
	Use encrypted instead of raw to make your intent clear that  zrepl  must
	only replicate filesystems that are actually encrypted by OpenZFS native
	encryption.   It  is meant as a safeguard to prevent unintended sends of
	unencrypted filesystems in raw mode.

   send_properties
     Sends the dataset properties along with snapshots.  Please be careful  with
     this option and read the note on property replication below.

   backup_properties
     When  properties are modified on a filesystem that was received from a send
     stream with send.properties=true, ZFS archives the original received  value
     internally.   This also applies to inheriting or overriding properties dur-
     ing zfs receive.

     When sending those received filesystems another hop, the  backup_properties
     flag  instructs  ZFS  to  send the original property values rather than the
     current locally set values.

     This is useful for replicating properties across multiple levels of  backup
     machines.	 Example: Suppose we want to flow snapshots from Machine A to B,
     then from B to C.	A will enable the properties send option.  B  will  want
     to  override  critical  properties such as mountpoint or canmount.  But the
     job that replicates from B to C should be	sending  the  original	property
     values received from A.  Thus, B sets the backup_properties option.

     Please  be  careful with this option and read the note on property replica-
     tion below.

   large_blocks
     This flag should not be changed after  initial  replication.   Prior  to  -
     OpenZFS  commit  7bcb7f08	it was possible to change this setting which re-
     sulted in data loss on the receiver.  The commit in question is included in
     OpenZFS 2.0 and works around the problem by prohibiting receives of  incre-
     mental streams with a flipped setting.

     WARNING:
	This bug has not been fixed in the OpenZFS 0.8 releases which means that
	changing  this	flag  after initial replication might cause data loss on
	the receiver.

   Recv Options
     Sink and pull jobs have an optional recv configuration section:

	jobs:
	- type: pull
	  recv:
	    properties:
	      inherit:
		- "mountpoint"
	      override: {
		"org.openzfs.systemd:ignore": "on"
	      }
	    bandwidth_limit: ...
	    placeholder:
	      encryption: unspecified | off | inherit
	  ...

     Jump to properties , bandwidth_limit , and placeholder.

   properties
     override maps directly to the zfs recv -o flag.  Property name-value  pairs
     specified in this map will apply to all received filesystems, regardless of
     whether the send stream contains properties or not.

     inherit maps directly to the zfs recv -x flag.  Property names specified in
     this  list  will  be  inherited from the receiving side's parent filesystem
     (e.g. root_fs).

     With both options, the sending side's property value is still stored on the
     receiver, but the local override or inherit is the one that  takes  effect.
     You can send the original properties from the first receiver to another re-
     ceiver using send.backup_properties.

   A Note on Property Replication
     If  a  send  stream  contains  properties,  as  per send.send_properties or
     send.backup_properties, the default ZFS behavior is to use those properties
     on the receiving side, verbatim.

     In many use cases for zrepl, this can have devastating  consequences.   For
     example,  when  backing  up a filesystem that has mountpoint=/ to a storage
     server, that storage server's root filesystem will be shadowed by	the  re-
     ceived file system on some platforms.  Also, many scripts and tools use ZFS
     user properties for configuration and do not check the property source (lo-
     cal  vs. received).  If they are installed on the receiving side as well as
     the sending side, property replication could have unintended effects.

     zrepl currently does not provide any  automatic  safe-guards  for	property
     replication:

     * Make  sure  to read the entire man page on zfs recv (man zfs recv) before
       enabling this feature.

     * Use recv.properties.override whenever possible, e.g. for  mountpoint=none
       or canmount=off.

     * Use recv.properties.inherit if that makes more sense to you.

     Below  is	an non-exhaustive list of problematic properties.  Please open a
     pull request if you find a property that is missing from this list.   (Both
     with  regards  to	core ZFS tools and other software in the broader ecosys-
     tem.)

   Mount behaviour
     * mountpoint

     * canmount

     * overlay

     Note: Before OpenZFS 2.0.5, inheriting or overriding the  mountpoint  prop-
     erty on ZVOLs fails in zfs recv.  If you are on such an older version, con-
     sider creating separate zrepl jobs for your ZVOL and filesystem datasets.

   Systemd
     With  systemd,  you  should also consider the properties processed by the -
     zfs-mount-generator .

     Most notably:

     * org.openzfs.systemd:ignore

     * org.openzfs.systemd:wanted-by

     * org.openzfs.systemd:required-by

   Encryption
     If the sender filesystems are encrypted but the sender does plain sends and
     property replication is enabled, the receiver must  inherit  the  following
     properties:

     * keylocation

     * keyformat

     * encryption

   Sharing
     You  may  not  want the replicated filesystem shared in the same way as the
     source is.

     * sharenfs

     * sharesmb

   Placeholders
	placeholder:
	  encryption: unspecified | off | inherit

     During replication, zrepl creates placeholder  datasets  on  the  receiving
     side  if  the sending side's filesystems filter creates gaps in the dataset
     hierarchy.  This is generally fully transparent to the user.  However, with
     OpenZFS Native  Encryption,  placeholders	require  zrepl	user  attention.
     Specifically, the problem is that, when zrepl attempts to create the place-
     holder  dataset  on  the receiver, and that placeholder's parent dataset is
     encrypted, ZFS wants to inherit encryption to  the  placeholder.	This  is
     relevant to two use cases that zrepl supports:

     1. encrypted-send-to-untrusted-receiver  In this use case, the sender sends
	an encrypted send stream and the receiver doesn't have the key loaded.

     2. send-plain-encrypt-on-receive The receive-side root_fs	dataset  is  en-
	crypted, and the senders are unencrypted.  The key of root_fs is loaded,
	and  the  goal	is  that the plain sends (e.g., from production) are en-
	crypted on-the-fly during receive, with root_fs's key.

     For encrypted-send-to-untrusted-receiver, the placeholder datasets need  to
     be created with -o encryption=off.  Without it, creation would fail with an
     error,  indicating  that the placeholder's parent dataset's key needs to be
     loaded.  But we don't trust the receiver, so we can't expect that	to  ever
     happen.

     However,  for  send-plain-encrypt-on-receive,  we	cannot	set  -o  encryp-
     tion=off.	The reason is that if we did, any of the (non-placeholder) child
     datasets  below  the  placeholder	would  inherit	encryption=off,  thereby
     silently  breaking  our encrypt-on-receive use case.  So, to cover this use
     case, we need to create  placeholders  without  specifying  -o  encryption.
     This  will  make  zfs  create  inherit  the encryption mode from the parent
     dataset, and thereby transitively from root_fs.

     The zrepl config provides the recv.placeholder.encryption knob  to  control
     this behavior.  In undefined mode (default), placeholder creation bails out
     and asks the user to configure a behavior.  In off mode, the placeholder is
     created  with encryption=off, i.e., encrypted-send-to-untrusted-rceiver use
     case.  In inherit mode, the placeholder is created  without  specifying  -o
     encryption at all, i.e., the send-plain-encrypt-on-receive use case.

   Common Options
   Bandwidth Limit (send & recv)
	bandwidth_limit:
	  max: 23.5 MiB # -1 is the default and disabled rate limiting
	  bucket_capacity: # token bucket capacity in bytes; defaults to 128KiB

     Both  send  and  recv  can  be limited to a maximum bandwidth through band-
     width_limit.  For most users, it should be sufficient  to	just  set  band-
     width_limit.max.	The  bandwidth_limit.bucket_capacity refers to the token
     bucket size.

     The bandwidth limit only applies to the payload data, i.e.,  the  ZFS  send
     stream.   It  does not account for transport protocol overheads.  The scope
     is the job level, i.e., all concurrent sends or incoming receives of a  job
     share the bandwidth limit.

   Replication Options
	jobs:
	- type: push
	  filesystems: ...
	  replication:
	    protection:
	      initial:	   guarantee_resumability # guarantee_{resumability,incremental,nothing}
	      incremental: guarantee_resumability # guarantee_{resumability,incremental,nothing}
	    concurrency:
	      size_estimates: 4
	      steps: 1

	  ...

   protection option
     The  protection variable controls the degree to which a replicated filesys-
     tem is protected from getting out of sync through a zrepl pruner or  exter-
     nal tools that destroy snapshots.	zrepl can guarantee resumability or just
     incremental replication.

     guarantee_resumability  is the default value and guarantees that a replica-
     tion step is always resumable and that incremental replication will  always
     be   possible.   The  implementation  uses  replication  cursors,	last-re-
     ceived-hold and step holds.

     guarantee_incremental only guarantees that incremental replication will al-
     ways be possible.	If a step from -> to is interrupted and its to	snapshot
     is  destroyed,  zrepl  will  remove the half-received to's resume state and
     start a new step from -> to2.  The implementation uses replication cursors,
     tentative replication cursors and last-received-hold.

     guarantee_nothing does not make any  guarantees  with  regards  to  keeping
     sending  and  receiving side in sync.  No bookmarks or holds are created to
     protect sender and receiver from diverging.

     Tradeoffs

     Using guarantee_incremental instead of guarantee_resumability obviously re-
     moves the resumability guarantee.	This means that replication progress  is
     no  longer  monotonic  which  might  lead to a replication setup that never
     makes progress if mid-step interruptions are too  frequent  (e.g.	frequent
     network  outages).   However, the advantage and reason for existence of the
     incremental mode is that it allows the pruner to delete snapshots of inter-
     rupted replication steps which is useful if replication happens  so  rarely
     (or  fails  so frequently) that the amount of disk space exclusively refer-
     enced by the step's snapshots becomes intolerable.

     NOTE:
	When changing this flag, obsoleted  zrepl-managed  bookmarks  and  holds
	will  be  destroyed  on  the next replication step that is attempted for
	each filesystem.

   concurrency option
     The concurrency options control the maximum amount  of  concurrency  during
     replication.  The default values allow some concurrency during size estima-
     tion but no parallelism for the actual replication.

     * concurrency.steps  (default  =  1) controls the maximum number of concur-
       rently executed replication steps.  The planning step for each file  sys-
       tem is counted as a single step.

     * concurrency.size_estimates  (default  = 4) controls the maximum number of
       concurrent step size estimations done by the job.

     Note that initial replication cannot start  replicating  child  filesystems
     before the parent filesystem's initial replication step has completed.

     Some notes on tuning these values:

     * Disk:  Size  estimation is less I/O intensive than step execution because
       it does not need to access the data blocks.

     * CPU: Size estimation is usually a dense CPU burst whereas step  execution
       CPU  utilization  is  stretched out over time because of disk IO.  Faster
       disks, sending a compressed dataset in plain mode and the zrepl transport
       mode all contribute to higher CPU requirements.

     * Network bandwidth: Size estimation does not consume meaningful amounts of
       bandwidth, step execution does.

     * zrepl ZFS abstractions: for each replication step zrepl needs  to  update
       its  ZFS  abstractions through the zfs command which often waits multiple
       seconds for the zpool to sync.  Thus, if the actual send & recv time of a
       step is small compared to the time spent on zrepl ZFS  abstractions  then
       increasing  step  execution  concurrency  will  result in a lower overall
       turnaround time.

   Conflict Resolution Options
	jobs:
	- type: push
	  filesystems: ...
	  conflict_resolution:
	    initial_replication: most_recent | all | fail # default: most_recent

	  ...

   initial_replication option
     The initial_replication option determines how many snapshots  zrepl  repli-
     cates  if	the  filesystem  has not been replicated before.  If most_recent
     (the default), the initial replication will only transfer the  most  recent
     snapshot,	while  ignoring  previous snapshots.  If all snapshots should be
     replicated, specify all.  Use fail to make replication  of  the  filesystem
     fail in case there is no corresponding fileystem on the receiver.

     For  example,  suppose  there  are  snapshosts  tank@1, tank@2, tank@3 on a
     sender.  Then most_recent will replicate just @3, but  all  will  replicate
     @1, @2, and @3.

     If  initial  replication  is  interrupted, and there is at least one (maybe
     partial) snapshot on the receiver, zrepl will always resume in  incremental
     mode.   And  that is regardless of where the initial replication was inter-
     rupted.

     For example, if initial_replication: all and the transfer of @1  is  inter-
     rupted,  zrepl  would retry/resume at @1.	And even if the user changes the
     config to initial_replication:  most_recent  before  resuming,  incremental
     mode will still resume at @1.

   Taking Snaphots
     You  can  configure  zrepl  to  take  snapshots  of  the filesystems in the
     filesystems field specified in push, source and snap jobs.

     The following snapshotting types are supported:
		+-------------------+-----------------------------+
		| snapshotting.type | Comment			  |
		+-------------------+-----------------------------+
		| periodic	    | Ensure that  snapshots  are |
		|		    | taken  at  a particular in- |
		|		    | terval.			  |
		+-------------------+-----------------------------+
		| cron		    | Use cron spec to take snap- |
		|		    | shots at particular  points |
		|		    | in time.			  |
		+-------------------+-----------------------------+
		| manual	    | zrepl  does  not	take  any |
		|		    | snapshots by itself.	  |
		+-------------------+-----------------------------+

     The periodic and cron snapshotting types share some common options and  be-
     havior:

     * Naming: The snapshot names are composed of a user-defined prefix followed
       by  a  UTC date formatted like 20060102_150405_000 by default.  We either
       use UTC or timestamp with timezone information because it will avoid name
       conflicts when switching time zones or between summer  and  winter  time.
       Note that if timestamp with time zone information is used, the "+" of the
       timezone (i.e. +02:00) is replaced by "_" (i.e. _02:00) to conform to al-
       lowed characters in ZFS snapshots.

     * Hooks:  You  can  configure  hooks to run before or after zrepl takes the
       snapshots. See below for details.

     * Push replication: After creating all snapshots, the snapshotter will wake
       up the replication part of the job, if it's a push job.	Note that  snap-
       shotting  is decoupled from replication, i.e., if it is down or takes too
       long, snapshots will still be taken.  Note further that	other  jobs  are
       not woken up by snapshotting.

     NOTE:
	    There  is  no concept of ownership of the snapshots that are created
	    by	periodic  or  cron.   Thus,  there  is	no  distinction  between
	    zrepl-created  snapshots  and user-created snapshots during replica-
	    tion or pruning.

	    In particular, pruning will take all snapshots into consideration by
	    default.  To constrain pruning to just zrepl-created snapshots:

	       1. Assign a unique prefix to the snapshotter and

	       2. Use the regex functionality of the various pruning keep  rules
		  to just consider snapshots with that prefix.

	There is currently no way to constrain replication to just zrepl-created
	snapshots.   Follow and comment at issue #403 if you need this function-
	ality.

     NOTE:
	The zrepl signal wakeup JOB subcommand does not trigger snapshotting.

   periodic Snapshotting
	jobs:
	- ...
	  filesystems: { ... }
	  snapshotting:
	    type: periodic
	    prefix: zrepl_
	    interval: 10m
	    # Timestamp format that is used as snapshot suffix.
	    # Can be any of "dense" (default), "human", "iso-8601", "unix-seconds" or a custom Go time format (see https://go.dev/src/time/format.go)
	    timestamp_format: dense
	    # Specifies in which time zone the snapshot suffix is generated, optional, defaults to UTC, used only if time zone information is part of timestamp_format.
	    # Can be "UTC" (default), "Local" (time zone information from the OS) or any of the IANA Time Zone names (see https://nodatime.org/TimeZones)
	    timestamp_location: UTC
	    hooks: ...
	 pruning: ...

     The periodic snapshotter ensures that snapshots are taken in the  specified
     interval.	 If you use zrepl for backup, this translates into your recovery
     point objective (RPO).  To meet your RPO, you still need  to  monitor  that
     replication, which happens asynchronously to snapshotting, actually works.

     It  is  desirable to get all filesystems snapshotted simultaneously because
     it results in a more consistent backup.  To  accomplish  this  while  still
     maintaining  the  interval,  the  periodic  snapshotter attempts to get the
     snapshotting rhythms in sync.  To find that sync  point,  the  most  recent
     snapshot,	created by the snapshotter, in any of the matched filesystems is
     used.  A filesystem that does not have snapshots  by  the	snapshotter  has
     lower  priority  than filesystem that do, and thus might not be snapshotted
     (and replicated) until it is snapshotted at the next sync point.  The snap-
     shotter uses the prefix to identify which snapshots it created.

   cron Snapshotting
	jobs:
	- type: snap
	  filesystems: { ... }
	  snapshotting:
	    type: cron
	    prefix: zrepl_
	    # (second, optional) minute hour day-of-month month day-of-week
	    # This example takes snapshots daily at 3:00.
	    cron: "0 3 * * *"
	    # Timestamp format that is used as snapshot suffix.
	    # Can be any of "dense" (default), "human", "iso-8601", "unix-seconds" or a custom Go time format (see https://go.dev/src/time/format.go)
	    timestamp_format: dense
	    # Specifies in which time zone the snapshot suffix is generated, optional, defaults to UTC, used only if time zone information is part of timestamp_format.
	    # Can be "UTC" (default), "Local" (time zone information from the OS) or any of the IANA Time Zone names (see https://nodatime.org/TimeZones)
	    timestamp_location: UTC
	  pruning: ...

     In cron mode, the snapshotter takes snaphots at fixed points in time.   See
     https://en.wikipedia.org/wiki/Cron  for  details on the syntax.  zrepl uses
     the the github.com/robfig/cron/v3 Go  package  for  parsing.   An	optional
     field  for  "seconds" is supported to take snapshots at sub-minute frequen-
     cies.

   manual Snapshotting
	jobs:
	- type: push
	  snapshotting:
	    type: manual
	  ...

     In manual mode, zrepl does not take snapshots by itself.  Manual  snapshot-
     ting  is  most useful if you have existing infrastructure for snapshot man-
     agement.  Or, if you want to decouple snapshot management from  replication
     using a zrepl snap job.  See this quickstart guide for an example.

     To  trigger replication after taking snapshots, use the zrepl signal wakeup
     JOB command.

   Timestamp Format
     The cron and periodic snapshotter support configuring  a  custom  timestamp
     format that is used as suffix for the snapshot name.

     By  default,  the current time is converted to UTC and then formatted using
     the dense format.

     Both time zone and format can be  customized  by  setting	timestamp_format
     and/or timestamp_location.

     timestamp_location is fed verbatim into time.LoadLocation.
	timestamp_format supports the following values (case-insensitive).

     * dense => Go format string 20060102_150405_000

     * human => Go format string 2006-01-02_15:04:05

     * iso-8601 => Go format string 2006-01-02T15:04:05.000Z07:00

     * unix-seconds  =>  Unix  seconds	since  the epoch, formatted as a decimal
       string (time.Time.Unix)

     * Any custom Go time format accepted by time.Time#Format.

     The dense, human, and unix-seconds formats require  the  timestamp_location
     to be UTC because they do not contain timezone information.

     The  +  character is not allowed in ZFS snapshot names.  Format strings are
     disallowed to contain the + character (it's not a character that is  inter-
     preted  by  time.Time.Format, so there's no reason to use it).  If a format
     _produces_ a +, that + is replaced by an _` character.

   Pre- and Post-Snapshot Hooks
     Jobs with periodic snapshots can run hooks before and/or after  taking  the
     snapshot  specified  in snapshotting.hooks: Hooks are called per filesystem
     before and after the snapshot is taken (pre- and post-edge).  Pre-edge  in-
     vocations	are in configuration order, post-edge invocations in reverse or-
     der, i.e. like a stack.  If a  pre-snapshot  invocation  fails,  err_is_fa-
     tal=true  cuts off subsequent hooks, does not take a snapshot, and only in-
     vokes  post-edges	 corresponding	 to   previous	 successful   pre-edges.
     err_is_fatal=false  logs the failed pre-edge invocation but does not affect
     subsequent hooks nor snapshotting itself.	Post-edges are only invoked  for
     hooks  whose  pre-edges ran without error.  Note that hook failures for one
     filesystem never affect other filesystems.

     The optional timeout parameter specifies a period after  which  zrepl  will
     kill  the	hook process and report an error.  The default is 30 seconds and
     may be specified in any units understood by time.ParseDuration.

     The optional filesystems filter which limits the filesystems the hook  runs
     for. This uses the same filter specification as jobs.

     Most  hook types take additional parameters, please refer to the respective
     subsections below.
	      +---------------------+---------+----------------------+
	      | Hook type	    | Details | Description	     |
	      +---------------------+---------+----------------------+
	      | command 	    | Details | Arbitrary  pre-  and |
	      | 		    |	      | post	    snapshot |
	      | 		    |	      | scripts.	     |
	      +---------------------+---------+----------------------+
	      | postgres-checkpoint | Details | Execute     Postgres |
	      | 		    |	      | CHECKPOINT  SQL com- |
	      | 		    |	      | mand  before   snap- |
	      | 		    |	      | shot.		     |
	      +---------------------+---------+----------------------+
	      | mysql-lock-tables   | Details | Flush  and read-Lock |
	      | 		    |	      | MySQL  tables  while |
	      | 		    |	      | taking the snapshot. |
	      +---------------------+---------+----------------------+

   command Hooks
	jobs:
	- type: push
	  filesystems: {
	    "<": true,
	    "tmp": false
	  }
	  snapshotting:
	    type: periodic
	    prefix: zrepl_
	    interval: 10m
	    hooks:
	    - type: command
	      path: /etc/zrepl/hooks/zrepl-notify.sh
	      timeout: 30s
	      err_is_fatal: false
	    - type: command
	      path: /etc/zrepl/hooks/special-snapshot.sh
	      filesystems: {
		"tank/special": true
	      }
	  ...

     command  hooks take a path to an executable script or binary to be executed
     before  and  after   the	snapshot.    path   must   be	absolute   (e.g.
     /etc/zrepl/hooks/zrepl-notify.sh).  No arguments may be specified; create a
     wrapper  script  if  zrepl must call an executable that requires arguments.
     The process standard output is logged at  level  INFO.  Standard  error  is
     logged at level WARN.  The following environment variables are set:

     * ZREPL_HOOKTYPE: either "pre_snapshot" or "post_snapshot"

     * ZREPL_FS: the ZFS filesystem name being snapshotted

     * ZREPL_SNAPNAME:	   the	   zrepl-generated     snapshot    name    (e.g.
       zrepl_20380119_031407_000)

     * ZREPL_DRYRUN: set to "true" if a dry run is in progress	so  scripts  can
       print, but not run, their commands

     An      empty	template      hook     can     be     found	in     -
     internal/config/samples/hooks/template.sh.

   postgres-checkpoint Hook
     Connects to  a  Postgres  server  and  executes  the  CHECKPOINT  statement
     pre-snapshot.  Checkpointing applies the WAL contents to all data files and
     syncs  the data files to disk.  This is not required for a consistent data-
     base backup: it merely forward-pays the "cost" of WAL replay to the time of
     snapshotting instead of at restore.  However, the	Postgres  manual  recom-
     mends  against  checkpointing during normal operation.  Further, the opera-
     tion requires Postgres superuser privileges.  zrepl users	must  decide  on
     their own whether this hook is useful for them (it likely isn't).

     ATTENTION:
	Note  that  WALs  and  Postgres  data  directory (with all database data
	files)	must  be  on  the  same  filesystem  to  guarantee   a	 correct
	point-in-time backup with the ZFS snapshot.

     DSN syntax documented here: https://godoc.org/github.com/lib/pq

	CREATE USER zrepl_checkpoint PASSWORD yourpasswordhere;
	ALTER ROLE zrepl_checkpoint SUPERUSER;

	- type: postgres-checkpoint
	  dsn: "host=localhost port=5432 user=postgres password=yourpasswordhere sslmode=disable"
	  filesystems: {
	      "p1/postgres/data11": true
	  }

   mysql-lock-tables Hook
     Connects to MySQL and executes

     * pre-snapshot  FLUSH TABLES WITH READ LOCK to lock all tables in all data-
       bases in the MySQL server we connect to (docs)

     * post-snapshot UNLOCK TABLES  reverse above operation.

     Above procedure is documented in the MySQL manual as a means to  produce  a
     consistent backup of a MySQL DBMS installation (i.e., all databases).

     DSN	 syntax:	 [username[:password]@][protocol[(address)]]/db-
     name[?param1=value1&...&paramN=valueN]

     ATTENTION:
	All MySQL databases must be on the same ZFS filesystem	to  guarantee  a
	consistent point-in-time backup with the ZFS snapshot.

	CREATE USER zrepl_lock_tables IDENTIFIED BY 'yourpasswordhere';
	GRANT RELOAD ON *.* TO zrepl_lock_tables;
	FLUSH PRIVILEGES;

	- type: mysql-lock-tables
	  dsn: "zrepl_lock_tables:yourpasswordhere@tcp(localhost)/"
	  filesystems: {
	    "tank/mysql": true
	  }

   Pruning Policies
     In  zrepl, pruning means destroying snapshots.  Pruning must happen on both
     sides of a replication or the systems would  inevitably  run  out	of  disk
     space at some point.

     Typically,  the  requirements  to temporal resolution and maximum retention
     time differ per side.  For example, when using zrepl  to  back  up  a  busy
     database server, you will want high temporal resolution (snapshots every 10
     min)  for	the last 24h in case of administrative disasters, but cannot af-
     ford to store them for much longer because you  might  have  high	turnover
     volume  in  the  database.   On  the receiving side, you may have more disk
     space available, or need to comply with other backup retention policies.

     zrepl uses a set of  keep rules per sending and receiving side to determine
     which snapshots shall be kept per filesystem.  A snapshot that is not  kept
     by  any rule is destroyed.  The keep rules are evaluated on the active side
     (push or pull job) of the replication setup, for both  active  and  passive
     side,  after  replication completed or was determined to have failed perma-
     nently.

     Example Configuration:

	jobs:
	  - type: push
	    name: ...
	    connect: ...
	    filesystems: {
	      "<": true,
	      "tmp": false
	    }
	    snapshotting:
	      type: periodic
	      prefix: zrepl_
	      interval: 10m
	    pruning:
	      keep_sender:
		- type: not_replicated
		# make sure manually created snapshots by the administrator are kept
		- type: regex
		  regex: "^manual_.*"
		- type: grid
		  grid: 1x1h(keep=all) | 24x1h | 14x1d
		  regex: "^zrepl_.*"
	      keep_receiver:
		- type: grid
		  grid: 1x1h(keep=all) | 24x1h | 35x1d | 6x30d
		  regex: "^zrepl_.*"
		# manually created snapshots will be kept forever on receiver
		- type: regex
		  regex: "^manual_.*"

     DANGER:
	You might have existing snapshots of  filesystems  affected  by  pruning
	which  you  want  to keep, i.e. not be destroyed by zrepl.  Make sure to
	actually add the necessary regex keep rules on	both  sides,  like  with
	manual in the example above.

   Policy not_replicated
	jobs:
	- type: push
	  pruning:
	    keep_sender:
	    - type: not_replicated
	  ...

     not_replicated keeps all snapshots that have not been replicated to the re-
     ceiving   side.   It  only  makes	sense  to  specify  this  rule	for  the
     keep_sender.  The reason is that, by definition, all snapshots on	the  re-
     ceiver have already been replicated to there from the sender.  To determine
     whether  a sender-side snapshot has already been replicated, zrepl uses the
     replication cursor bookmark which corresponds to the most	recent	success-
     fully replicated snapshot.

     ATTENTION:
	If  you  use  not_replicated in your pruning rules, make sure to monitor
	replication.  If your replication gets stuck then not_replicated  causes
	snapshots to pile uip on the sender.  ZFS and especially the zfs manage-
	ment  command  are  known  to degrade in performance with a lot of snap-
	shots.	Such degradation impacts zrepl, any  other  scripts,  and  human
	ability to manage your zpool.

   Policy grid
	jobs:
	- type: pull
	  pruning:
	    keep_receiver:
	    - type: grid
	      regex: "^zrepl_.*"
	      grid: 1x1h(keep=all) | 24x1h | 35x1d | 6x30d
		    a		     a		     a
		    aa 1 repetition of a one-hour interval with keep=all
				     a		     a
				     aa 24 repetitions of a one-hour interval with keep=1
						     a
						     aa 6 repetitions of a 30-day interval with keep=1
	  ...

     The  retention  grid can be thought of as a time-based sieve that thins out
     snapshots as they get older.

     The grid field specifies a list of adjacent time intervals.  Each	interval
     is  a bucket with a maximum capacity of keep snapshots.  The following pro-
     cedure happens during pruning:

     1. The list of snapshots is filtered by the regular  expression  in  regex.
	Only  snapshots names that match the regex are considered for this rule,
	all others will be pruned unless another rule keeps them.

     2. The snapshots that match regex are placed onto a time axis according  to
	their  creation  date.	The youngest snapshot is on the left, the oldest
	on the right.

     3. The first buckets are placed "under" that axis so that the  grid  spec's
	first bucket's left edge aligns with youngest snapshot.

     4. All subsequent buckets are placed adjacent to their predecessor bucket.

     5. Now  each  snapshot  on  the  axis either falls into one bucket or it is
	older  than  our  rightmost  bucket.   Buckets	are  left-inclusive  and
	right-exclusive  which	means that a snapshot on the edge of bucket will
	always 'fall into the right one'.

     6. Snapshots older than the rightmost bucket are not kept by the grid spec-
	ification.

     7. For each bucket, we only keep the keep oldest snapshots.

     The syntax to describe the bucket list is as follows:

	Repeat x Duration (keep=all)

     * The duration specifies the length of the interval.

     * The keep count specifies the  number  of  snapshots  that  fit  into  the
       bucket.	 It  can  be either a positive integer or all (all snapshots are
       kept).

     * The repeat count repeats the bucket definition for the  specified  number
       of times.

     Example:

	Assume the following grid specification:

	   grid: 1x1h(keep=all) | 2x2h | 1x3h

	This grid specification produces the following constellation of buckets:

	0h	  1h	    2h	      3h	4h	  5h	    6h	      7h	8h	  9h
	|	  |	    |	      | 	|	  |	    |	      | 	|	  |
	|-Bucket1-|-----Bucket2-------|------Bucket3------|-----------Bucket4-----------|
	| keep=all|	 keep=1       |       keep=1	  |	       keep=1		|

	Now assume that we have a set of snapshots @a, @b, ..., @D.
	Snapshot @a is the most recent snapshot.
	Snapshot @D is the oldest snapshot, it is almost 9 hours older than snapshot @a.
	We place the snapshots on the same timeline as the buckets:

	0h	  1h	    2h	      3h	4h	  5h	    6h	      7h	8h	  9h
	|	  |	    |	      | 	|	  |	    |	      | 	|	  |
	|-Bucket1-|-----Bucket2-------|------Bucket3------|-----------Bucket4-----------|
	| keep=all|	 keep=1       |       keep=1	  |	       keep=1		|
	|	  |		      | 		  |				|
	| a  b	c | d  e  f  g	h  i  j  k  l  m  n  o	p |q  r  s  t  u  v  w	x  y  z |A  B  C  D

	We obtain the following mapping of snapshots to buckets:

	Bucket1:   a,b,c
	Bucket2:   d,e,f,g,h,i
	Bucket3:   j,k,l,m,n,o,p
	Bucket4:   q,r,s,t,u,v,w,x,y,z
	No bucket: A,B,C,D

	For each bucket, we now prune snapshots until it only contains `keep` snapshots.
	Newer snapshots are destroyed first.
	Snapshots that do not fall into a bucket are always destroyed.

	Result after pruning:

	0h	  1h	    2h	      3h	4h	  5h	    6h	      7h	8h	  9h
	|	  |	    |	      | 	|	  |	    |	      | 	|	  |
	|-Bucket1-|-----Bucket2-------|------Bucket3------|-----------Bucket4-----------|
	|	  |		      | 		  |				|
	| a  b	c |		   i  | 		p |			      z |

   Policy last_n
	jobs:
	  - type: push
	    pruning:
	      keep_receiver:
	      - type: last_n
		count: 10
		regex: ^zrepl_.*$ # optional
	  ...

     last_n  filters the snapshot list by regex, then keeps the last count snap-
     shots in that list (last = youngest = most recent creation date) All  snap-
     shots  that  don't match regex or exceed count in the filtered list are de-
     stroyed unless matched by other rules.

   Policy regex
	jobs:
	  - type: push
	    pruning:
	      keep_receiver:
	      # keep all snapshots with prefix zrepl_ or manual_
	      - type: regex
		regex: "^(zrepl|manual)_.*"

	  - type: push
	    snapshotting:
	      prefix: zrepl_
	    pruning:
	      keep_sender:
	      # keep all snapshots that were not created by zrepl
	      - type: regex
		negate: true
		regex: "^zrepl_.*"

     regex keeps all snapshots whose names are matched by the regular expression
     in regex.	Like all other regular	expression  fields  in	prune  policies,
     zrepl uses Go's regexp.Regexp Perl-compatible regular expressions (Syntax).
     The optional negate boolean field inverts the semantics: Use it if you want
     to keep all snapshots that do not match the given regex.

   Source-side snapshot pruning
     A	source jobs takes snapshots on the system it runs on.  The corresponding
     pull job on the replication target connects to the source	job  and  repli-
     cates  the snapshots.  Afterwards, the pull job coordinates pruning on both
     sender (the source job side) and receiver (the pull job side).

     There is no built-in way to define and execute pruning on the  source  side
     independently  of the pull side.  The source job will continue taking snap-
     shots which will not be pruned until the pull side  connects.   This  means
     that  extended  replication  downtime  will fill up the source's zpool with
     snapshots.

     If the above is a conceivable situation for you, consider using push  mode,
     where pruning happens on the same side where snapshots are taken.

   Workaround using snap job
     As  a  workaround (see GitHub issue #102 for development progress), a prun-
     ing-only snap job can be defined on the source side: The  snap  job  is  in
     charge of snapshot creation & destruction, whereas the source job's role is
     reduced  to  just serving snapshots.  However, since, jobs are run indepen-
     dently, it is possible that the snap job  will  prune  snapshots  that  are
     queued  for  replication / destruction by the remote pull job that connects
     to the source job.  Symptoms of such race conditions are spurious	replica-
     tion and destroy errors.

     Example configuration:

	# source side
	jobs:
	- type: snap
	  snapshotting:
	    type: periodic
	  pruning:
	    keep:
	      # source side pruning rules go here
	  ...

	- type: source
	  snapshotting:
	    type: manual
	  root_fs: ...

	# pull side
	jobs:
	- type: pull
	  pruning:
	    keep_sender:
	      # let the source-side snap job do the pruning
	      - type: regex
		regex: ".*"
	      ...
	    keep_receiver:
	      # feel free to prune on the pull side as desired
	      ...

   Logging
     zrepl  uses structured logging to provide users with easily processable log
     messages.

     Logging outlets are configured in the global section of the config file.

	global:
	  logging:

	    - type: OUTLET_TYPE
	      level: MINIMUM_LEVEL
	      format: FORMAT

	    - type: OUTLET_TYPE
	      level: MINIMUM_LEVEL
	      format: FORMAT

	    ...

	jobs: ...

     ATTENTION:
	The first outlet is special: if an error writing to any  outlet  occurs,
	the  first  outlet receives the error and can print it.  Thus, the first
	outlet must be the one that always works and does not block,  e.g.  std-
	out, which is the default.

   Default Configuration
     By default, the following logging configuration is used

	global:
	  logging:

	    - type: "stdout"
	      level:  "warn"
	      format: "human"

   Building Blocks
     The  following sections document the semantics of the different log levels,
     formats and outlet types.

   Levels
		      +-------+-------+----------------------+
		      | Level | SHORT | Description	     |
		      +-------+-------+----------------------+
		      | error | ERRO  | immediate action re- |
		      |       |       | quired		     |
		      +-------+-------+----------------------+
		      | warn  | WARN  | symptoms for miscon- |
		      |       |       | figuration, soon ex- |
		      |       |       | pected failure, etc. |
		      +-------+-------+----------------------+
		      | info  | INFO  | explains  what	hap- |
		      |       |       | pens   without	 too |
		      |       |       | much detail	     |
		      +-------+-------+----------------------+
		      | debug | DEBG  | tracing information, |
		      |       |       | state  dumps,	etc. |
		      |       |       | useful	 for  debug- |
		      |       |       | ging.		     |
		      +-------+-------+----------------------+

     Incorrectly classified messages are considered a  bug  and  should  be  re-
     ported.

   Formats
		      +--------+-----------------------------+
		      | Format | Description		     |
		      +--------+-----------------------------+
		      | human  | prints  job  and  subsystem |
		      |        | into  brackets  before  the |
		      |        | actual message, followed by |
		      |        | remaining  fields in logfmt |
		      |        | style			     |
		      +--------+-----------------------------+
		      | logfmt | logfmt output. zrepl uses - |
		      |        | this Go package.	     |
		      +--------+-----------------------------+
		      | json   | JSON formatted output. Each |
		      |        | line is a valid JSON  docu- |
		      |        | ment.	Fields are marshaled |
		      |        | by encoding/json.Marshal(), |
		      |        | which is particularly	use- |
		      |        | ful  for  processing in log |
		      |        | aggregation  or  when	pro- |
		      |        | cessing state dumps.	     |
		      +--------+-----------------------------+

   Outlets
     Outlets are the destination for log entries.

   stdout Outlet
		    +-----------+-----------------------------+
		    | Parameter | Comment		      |
		    +-----------+-----------------------------+
		    | type	| stdout		      |
		    +-----------+-----------------------------+
		    | level	| minimum  log level	      |
		    +-----------+-----------------------------+
		    | format	| output format 	      |
		    +-----------+-----------------------------+
		    | time	| always include time in out- |
		    |		| put (true or false)	      |
		    +-----------+-----------------------------+
		    | color	| colorize  output  according |
		    |		| to  log  level   (true   or |
		    |		| false)		      |
		    +-----------+-----------------------------+

     Writes all log entries with minimum level level formatted by format to std-
     out.   If	stdout	is a tty, interactive usage is assumed and both time and
     color are set to true.

     Can only be specified once.

   syslog Outlet
		  +----------------+-----------------------------+
		  | Parameter	   | Comment			 |
		  +----------------+-----------------------------+
		  | type	   | syslog			 |
		  +----------------+-----------------------------+
		  | level	   | minimum  log level 	 |
		  +----------------+-----------------------------+
		  | format	   | output format		 |
		  +----------------+-----------------------------+
		  | facility	   | Which  syslog  facility  to |
		  |		   | use (default = local0)	 |
		  +----------------+-----------------------------+
		  | retry_interval | Interval  between reconnec- |
		  |		   | tion  attempts  to   syslog |
		  |		   | (default = 0)		 |
		  +----------------+-----------------------------+

     Writes  all  log  entries formatted by format to syslog.  On normal setups,
     you should not need to change the retry_interval.

     Can only be specified once.

   tcp Outlet
		  +----------------+-----------------------------+
		  | Parameter	   | Comment			 |
		  +----------------+-----------------------------+
		  | type	   | tcp			 |
		  +----------------+-----------------------------+
		  | level	   | minimum  log level 	 |
		  +----------------+-----------------------------+
		  | format	   | output format		 |
		  +----------------+-----------------------------+
		  | net 	   | tcp in most cases		 |
		  +----------------+-----------------------------+
		  | address	   | remote    network,     e.g. |
		  |		   | logs.example.com:10202	 |
		  +----------------+-----------------------------+
		  | retry_interval | Interval  between reconnec- |
		  |		   | tion attempts to address	 |
		  +----------------+-----------------------------+
		  | tls 	   | TLS config (see below)	 |
		  +----------------+-----------------------------+

     Establishes a TCP connection to address and sends log messages with minimum
     level level formatted by format.  If tls is not specified,  an  unencrypted
     connection  is established.  If tls is specified, the TCP connection is se-
     cured with TLS + Client Authentication.  The latter is particularly  useful
     in combination with log aggregation services.
		    +-----------+-----------------------------+
		    | Parameter | Description		      |
		    +-----------+-----------------------------+
		    | ca	| PEM-encoded certificate au- |
		    |		| thority that signed the re- |
		    |		| mote	server's TLS certifi- |
		    |		| cate			      |
		    +-----------+-----------------------------+
		    | cert	| PEM-encoded client certifi- |
		    |		| cate identifying this zrepl |
		    |		| daemon  toward  the  remote |
		    |		| server		      |
		    +-----------+-----------------------------+
		    | key	| PEM-encoded,	  unencrypted |
		    |		| client private key  identi- |
		    |		| fying this zrepl daemon to- |
		    |		| ward the remote server      |
		    +-----------+-----------------------------+

     WARNING:
	zrepl  drops log messages to the TCP outlet if the underlying connection
	is not fast enough.  Note that TCP buffering in the  kernel  must  first
	run full before messages are dropped.

	Make  sure to always configure a stdout outlet as the special error out-
	let to be informed about problems with the TCP outlet (see above ).

     NOTE:
	zrepl uses Go's crypto/tls and crypto/x509 packages and leaves	all  but
	the required fields in tls.Config at their default values.  In case of a
	security  defect  in  these packages, zrepl has to be rebuilt because Go
	binaries are statically linked.

   Monitoring
     Monitoring endpoints are configured in the global.monitoring section of the
     config file.

   Prometheus & Grafana
     zrepl can expose Prometheus metrics via HTTP.  The listen attribute is a  -
     net.Listen  string for tcp, e.g. :9811 or 127.0.0.1:9811 (port 9811 was re-
     served  to  zrepl	on the official list).	The listen_freebind attribute is
     explained here.  The Prometheus monitoring job appears in the zrepl control
     job list and may be specified at most once.

     zrepl also ships with an importable Grafana  dashboard  that  consumes  the
     Prometheus metrics: see dist/grafana.  The dashboard also contains some ad-
     vice on which metrics are important to monitor.

     NOTE:
	At  the time of writing, there is no stability guarantee on the exported
	metrics.

	global:
	  monitoring:
	    - type: prometheus
	      listen: ':9811'
	      listen_freebind: true # optional, default false

   Miscellaneous
   Runtime Directories & UNIX Sockets
     The zrepl daemon needs to open various UNIX sockets in a runtime directory:

     * a control socket that the CLI commands use to interact with the daemon

     * the ssh+stdinserver Transport listener opens one  socket  per  configured
       client, named after client_identity parameter

     There  is	no  authentication on these sockets except the UNIX permissions.
     The zrepl daemon will refuse to bind any of the above sockets in  a  direc-
     tory that is world-accessible.

     The  following  sections of the global config shows the default paths.  The
     shell script below shows how the default runtime directory can be created.

	global:
	  control:
	    sockpath: /var/run/zrepl/control
	  serve:
	    stdinserver:
	      sockdir: /var/run/zrepl/stdinserver

	mkdir -p /var/run/zrepl/stdinserver
	chmod -R 0700 /var/run/zrepl

   Durations & Intervals
     Interval & duration fields in job definitions, pruning configurations, etc.
     must match the following regex:

	var durationStringRegex *regexp.Regexp = regexp.MustCompile(`^\s*(\d+)\s*(s|m|h|d|w)\s*$`)
	// s = second, m = minute, h = hour, d = day, w = week (7 days)

   Usage
   CLI Overview
     NOTE:
	The zrepl binary is self-documenting: run zrepl help for an overview  of
	the  available subcommands or zrepl SUBCOMMAND --help for information on
	available flags, etc.
   +-------------------------+-------------------------------------------------+
   | Subcommand 	     | Description				       |
   +-------------------------+-------------------------------------------------+
   | zrepl help 	     | show subcommand overview 		       |
   +-------------------------+-------------------------------------------------+
   | zrepl daemon	     | run  the  daemon,  required		       |
   |			     | for all zrepl functionality		       |
   +-------------------------+-------------------------------------------------+
   | zrepl status	     | show  job activity, or with		       |
   |			     | --mode raw for JSON output		       |
   +-------------------------+-------------------------------------------------+
   | zrepl stdinserver	     | see ssh+stdinserver  Trans-		       |
   |			     | port					       |
   +-------------------------+-------------------------------------------------+
   | zrepl signal wakeup JOB | manually  trigger  replica-		       |
   |			     | tion + pruning of JOB			       |
   +-------------------------+-------------------------------------------------+
   | zrepl signal reset JOB  | manually   abort    current		       |
   |			     | replication  +  pruning	of		       |
   |			     | JOB					       |
   +-------------------------+-------------------------------------------------+
   | zrepl configcheck	     | check  if  config  can	be		       |
   |			     | parsed without errors			       |
   +-------------------------+-------------------------------------------------+
   | zrepl migrate	     | perform on-disk state / ZFS property migrations |
   |			     | (see changelog for details)		       |
   +-------------------------+-------------------------------------------------+
   | zrepl zfs-abstraction   | list  and remove zrepl's abstractions on top of |
   |			     | ZFS,  e.g.  holds  and  step   bookmarks   (see |
   |			     | overview )				       |
   +-------------------------+-------------------------------------------------+

   zrepl daemon
     All  actual  work	zrepl does is performed by a daemon process.  The daemon
     supports structured logging and provides monitoring endpoints.

     When installing from a package, the package maintainer should have provided
     an init script / systemd.service file.  You should thus be  able  to  start
     zrepl daemon using your init system.

     Alternatively, or for running zrepl in the foreground, simply execute zrepl
     daemon.   Note that you won't see much output with the default logging con-
     figuration:

     ATTENTION:
	Make sure to actually monitor the error level output of zrepl: some con-
	figuration errors will not make the daemon exit.

	Example: if the daemon cannot create the ssh+stdinserver Transport sock-
	ets in the runtime directory, it will emit an error message but not exit
	because other tasks such as periodic snapshots & pruning  are  of  equal
	importance.

   Restarting
     The  daemon  handles  SIGINT  and	SIGTERM for graceful shutdown.	Graceful
     shutdown means at worst that a job will not be rescheduled for the next in-
     terval.  The daemon exits as soon as all jobs have reported shut down.

   Systemd Unit File
     A systemd service definition template is available in  dist/systemd.   Note
     that some of the options only work on recent versions of systemd.	Any help
     & improvements are very welcome, see issue #145.

   Ops Runbooks
   Migrating Sending Side
     Objective:  Move  sending-side  zpool to new hardware.  Make the move fully
     transparent to the sending-side jobs.  After the move is  done,  all  send-
     ing-side  zrepl  jobs  should  continue to work as if the move had not hap-
     pened.  In particular, incremental replication should be able  to	pick  up
     where it left before the move.

     Suppose we want to migrate all data from one zpool oldpool to another zpool
     newpool.	A  possible  reason might be that we want to change RAID levels,
     ashift, or just migrate over to next-gen hardware.

     If the pool names are different, zrepl's matching between	sender	and  re-
     ceiver  dataset  will  break  becase the receive-side dataset names contain
     oldpool.  To avoid this, we will need the name of the  new  pool  to  match
     that of the old pool.  The following steps will accomplish this:

     1.  Stop zrepl.

     2.  Create the new pool: zpool create newpool ...

     3.  Take a snapshot of the old pool so that you have something that you can
	 zfs  send.   For  example,  run  zfs snapshot -r oldpool@migration_old-
	 pool_newpool.

     4.  Send all of the oldpool's datasets to the new pool: zfs  send	-R  old-
	 pool@migration_oldpool_newpool | zfs recv -F newpool

     5.  Export the old pool: zpool export oldpool

     6.  Export the new pool: zpool export newpool

     7.  (Optional)  Change  the name of the old pool to something that does not
	 conflict with the new pool.  We are going to use the name oldoldpool in
	 this example.	Use zpool import with no arguments to see the  pool  id.
	 Then zpool import <id> oldoldpool && zpool export oldoldpool.

     8.  Import  the  new  pool,  while changing the name to match the old pool:
	 zpool import newpool oldpool

     9.  Start zrepl again and wake up the relevant jobs.

     10. Use zrepl status or you monitoring to ensure  that  replication  works.
	 The best test is an end-to-end test where you write some junk data on a
	 sender  dataset and wait until a snapshot with that data appears on the
	 receiving side.

     11. Once you are confident that replication is working, you may dispose  of
	 the old pool.

     Note  that,  depending  on pruning rules, it will not be possible to switch
     back to the old pool seamlessly, i.e., without a full re-replication.

   Platform Tests
     Along with the main zrepl binary, we  release  the  platformtest  binaries.
     The  zrepl  platform tests are an integration test suite that is complemen-
     tary to the pure Go unit tests.  Any test that needs to interact  with  ZFS
     is a platform test.

     The  platform  need to run as root.  For each test, we create a fresh dummy
     zpool backed by a file-based vdev.  The file path, and  a	root  mountpoint
     for the dummy zpool, must be specified on the command line:

	mkdir -p /tmp/zreplplatformtest
	./platformtest \
	    -poolname 'zreplplatformtest' \  # <- name must contain zreplplatformtest
	    -imagepath /tmp/zreplplatformtest.img \ # <- zrepl will create the file
	    -mountpoint /tmp/zreplplatformtest # <- must exist

     WARNING:
	platformtest  will  unconditionally  overwrite the file at imagepath and
	unconditionally zpool destroy $poolname.  So,  don't  use  a  production
	poolname,  and consider running the test in a VM.  It'll be a lot faster
	as well because the underlying operations, zfs list in particular,  will
	be faster.

     While  the  platformtests	are  running, there will be a log of log output.
     After all tests have run, it prints a summary with a list of tests, grouped
     by result type (success, failure, skipped):

	PASSING TESTS:
	  github.com/zrepl/zrepl/platformtest/tests.BatchDestroy
	  github.com/zrepl/zrepl/platformtest/tests.CreateReplicationCursor
	  github.com/zrepl/zrepl/platformtest/tests.GetNonexistent
	  github.com/zrepl/zrepl/platformtest/tests.HoldsWork
	  ...
	  github.com/zrepl/zrepl/platformtest/tests.SendStreamNonEOFReadErrorHandling
	  github.com/zrepl/zrepl/platformtest/tests.UndestroyableSnapshotParsing
	SKIPPED TESTS:
	  github.com/zrepl/zrepl/platformtest/tests.SendArgsValidationEncryptedSendOfUnencryptedDatasetForbidden__EncryptionSupported_false
	FAILED TESTS: []

     If there is a failure, or a skipped test that you believe should  be  pass-
     ing,  re-run  the	test  suite, capture stderr & stdout to a text file, and
     create an issue on GitHub.

     To run a specific test case, or a subset of tests matched by regex, use the
     -run REGEX command line flag.

     To stop test execution at the first failing test, and  prevent  cleanup  of
     the dummy zpool, use the -failure.stop-and-keep-pool flag.

     To  build	the platformtests yourself, use make test-platform-bin.  There's
     also the make test-platform target to run the platform tests with a default
     command line.

   Talks & Presentations
     * Talk at OpenZFS Developer Summit 2018 of pre-release 0.1 ( 25min  Record-
       ing , Slides , Event )

     * Talk  at EuroBSDCon2017 FreeBSD DevSummit with live demo of zrepl 0.0.3 (
       55min Recording, Slides, Event )

       * Note: The remarks on keep_bookmarks are  irrelevant  as  of  zrepl  0.1
	 which	introduced  the zrepl-managed replication cursor bookmark.  Read
	 the Overview section to learn more.

   Changelog
   0.7.0
     * [FEATURE] Config file inclusion using  include  directive.   This  allows
       distributing zrepl job definitions across multiple YAML files in a conf.d
       style directory.  (4d6583e, thanks, @ZeyadTamimi).

     * [FEATURE] Add configurable timezone for snapshot name timestamps. The de-
       fault remains UTC. (b9b9ad1, thanks, @mjasnik).

     * [FEATURE]  Improve  Grafana  dashboard with additional panels and refine-
       ments (3f75127, thanks, @deajan).

     * [BUG] Fix replication of placeholder filesystems (5615f49).

     * [BUG] Avoid using the word "error" in info-level log lines about ZFS com-
       mand execution (d3b2295, thanks, @bakhtiyarneyman).

     * [BUG] Detect duplicate and internal  job  names	at  config  parse  time.
       zrepl  will  now refuse to load a config with duplicate or internally re-
       served job names, instead of failing later at daemon startup.  (860a9be).

     * [DOCS] Add installation instructions for  openSUSE  (40c4827,  thanks,  -
       @findesgh).

     * [DOCS] Improve documentation on send_properties configuration and NFS/SMB
       considerations (b5d8538, thanks, @Malvineous).

     * [DOCS]  Warn  more prominently about the risks of the not_replicated keep
       rule (affe00a, thanks, @wxiaoguang).

     * [DOCS]  Improve	TLS/EasyRSA  setup  instructions  (e524b60,  thanks,   -
       @alorimer).

     * [DOCS]  zrepl.github.io:  auto-publish  from master branch, retire stable
       branch (4f950bb).

     * [MAINT] zrepl status: switch from the unmaintained cview fork back to the
       actively maintained tview library (d7ede3f).

     * [MAINT] Update to Go 1.25 toolchain with Go 1.24 language level & Go  de-
       pendencies

     * [MAINT] Debian packages: proper bash completion placement (2923009)

     * [MAINT] RPM packages: AlmaLinux 8 for RPM builds (27f4ad1).

     * [MAINT] Improve build system & CI setup

     NOTE:
	zrepl is a spare-time project primarily developed by Christian Schwarz.
	You can support maintenance and feature development through one of the following services:
	Donate via Patreon Donate via GitHub Sponsors Donate via Liberapay Donate via PayPal
	Note that PayPal processing fees are relatively high for small donations.
	For SEPA wire transfer and commercial support, please contact Christian directly.

   0.6.1
     * [FEATURE] add metric to detect filesystems rules that don't match any lo-
       cal dataset (thanks, @gmekicaxcient).

     * [BUG] zrepl status: hide progress bar once all filesystems reach terminal
       state (thanks, @0x3333).

     * [BUG] handling of tenative cursor presence if protection strategy doesn't
       use it (issue #714).

     * [DOCS]  address	setup  with  two  or  more  external  disks  (thanks,  -
       @se-jaeger).

     * [DOCS] document replication and conflict_resolution  options  (thanks,  -
       @InsanePrawn).

     * [DOCS]  docs:  talks:  add  note  on  keep_bookmarks  option  (thanks,  -
       @skirmess).

     * [MAINT] dist: add openrc service file (thanks, @gramosg).

     * [MAINT] grafana: update dashboard to Grafana 9.3.6.

     * [MAINT] run platform tests as part of CI.

     * [MAINT] build: upgrade to Go 1.21 and update  golangci-lint;  minimum  Go
       version for builds is now 1.20

   0.6
     * [FEATURE] Schedule-based snapshotting using cron syntax instead of an in-
       terval.

     * [FEATURE]  Configurable initial replication policy.  When a filesystem is
       first replicated to a receiver, this  control  whether  just  the  newest
       snapshot will be replicated vs. all existing snapshots. Learn more in the
       docs.

     * [FEATURE]   Configurable   timestamp   format   for  snapshot  names  via
       timestamp_format (Thanks, @ydylla).

     * [FEATURE] Add ZREPL_DESTROY_MAX_BATCH_SIZE env var (default  0=unlimited)
       (Thanks, @3nprob).

     * [FEATURE] Add zrepl configcheck --skip-cert-check flag (Thanks, @cole-h).

     * [BUG] Fix resuming from interrupted replications that use send.raw on un-
       encrypted datasets.

       * The  send  options introduced in zrepl 0.4 allow users to specify addi-
	 tional zfs send flags for zrepl to use.  Before this fix, when  setting
	 send.raw=true	on  a  job  that  replicates unencrypted datasets, zrepl
	 would not allow an interrupted replication to resume.	The reason  were
	 overly cautious checks to support the send.encrypted option.

       * This  bugfix  removes	these checks from the replication planner.  This
	 makes send.encrypted a sender-side-only concern, much	like  all  other
	 send.* flags.

       * However,  this  means	that  the  zrepl  status  UI no longer indicates
	 whether a replication step uses encrypted sends or not.  The setting is
	 still effective though.

     * [BREAK]	  convert    Prometheus    metric    zrepl_version_daemon     to
       zrepl_start_time metric

       * The  metric still reports the zrepl version in a label.  But the metric
	 value is now the Unix timestamp at the time  the  daemon  was	started.
	 The Grafana dashboard in dist/grafana has been updated.

     * [BUG] transient zrepl status error: Post "http://unix/status": EOF

     * [BUG] don't treat receive-side bookmarks as a replication conflict.  This
       facilitates chaining of replication jobs. See issue #490.

     * [BUG]  workaround  for Go/gRPC problem on Illumos where zrepl would crash
       when using the local transport type (issue #598).

     * [BUG] fix active child tasks panic that	cold  occur  during  replication
       plannig (issue #193abbe)

     * [BUG] zrepl status off-by-one error in display of completed step count (-
       ce6701f)

     * [BUG] Allow using day & week units for snapshotting.interval (ffb1d89)

     * [DOCS] docs/overview improvements (Thanks, @jtagcat).

     * [MAINT] Update to Go 1.19.

   0.5
     * [FEATURE] Bandwidth limiting (Thanks, Prominic.NET, Inc.)

     * [FEATURE] zrepl status: use a * to indicate which filesystem is currently
       replicating

     * [FEATURE] include daemon environment variables in zrepl status (currently
       only in --raw)

     * [BUG] fix encrypt-on-receive + placeholders use case (issue #504)

       * Before  this  fix,  plain sends to a receiver with an encrypted root_fs
	 could be received unencrypted if zrepl needed to create placeholders on
	 the receiver.

       * Existing zrepl users should read the docs and check zfs get -r  encryp-
	 tion,zrepl:placeholder PATH_TO_ROOTFS on the receiver.

       * Thanks to @mologie and @razielgn for reporting and testing!

     * [BUG] Rename mis-spelled send option embbeded_data to embedded_data.

     * [BUG] zrepl status: replication step numbers should start at 1

     * [BUG] incorrect bandwidth averaging in zrepl status.

     * [BUG]  FreeBSD  with  OpenZFS  2.0: zrepl would wait indefinitely for zfs
       send to exit on timeouts.

     * [BUG] fix strconv.ParseInt: value out of range bug (and use  the  control
       RPCs).

     * [DOCS] improve description of multiple pruning rules.

     * [DOCS] document platform tests.

     * [DOCS]  quickstart:  make users aware that prune rules apply to all snap-
       shots.

     * [MAINT] some platformtests were broken.

     * [MAINT] FreeBSD: release armv7 and arm64 binaries.

     * [MAINT] apt repo: update instructions due to apt-key deprecation.

     Note to all users: please read up on the following  OpenZFS  bugs,  as  you
     might be affected:

     * ZFS send/recv with ashift 9->12 leads to data corruption.

     * Various bugs with encrypted send/recv (Leadership meeting notes)

     Finally,  I'd  like  to point you to the GitHub discussion about which bug-
     fixes and features should be prioritized in zrepl 0.6 and beyond!

   0.4.0
     * [FEATURE] support setting zfs send / recv flags in the config (send: -wL-
       cepbS , recv: -ox ).  Config docs here and here .

     * [FEATURE] parallel replication is now configurable (disabled by	default,
       config docs here ).

     * [FEATURE] New zrepl status UI:

       * Interactive job selection.

       * Interactively zrepl signal jobs.

       * Filter filesystems in the job view by name.

       * An  approximation  of the old UI is still included as --mode legacy but
	 will be removed in a future release of zrepl.

     * [BUG] Actually use concurrency when listing zrepl  abstractions	&  doing
       size  estimation.   These operations were accidentally made sequential in
       zrepl 0.3.

     * [BUG] Job hang-up during second replication attempt.

     * [BUG] Data races conditions in the dataconn rpc stack.

     * [MAINT] Update to protobuf v1.25 and grpc 1.35.

     For users who skipped the 0.3.1 update: please make sure your pruning  grid
     config  is correct.  The following bugfix in 0.3.1 caused problems for some
     users:

     * [BUG] pruning: grid:  add all snapshots that do not match  the  regex  to
       the rule's destroy list.

   0.3.1
     Mostly a bugfix release for zrepl 0.3.

     * [FEATURE] pruning: add optional regex field to last_n rule

     * [DOCS] pruning: grid : improve documentation and add an example

     * [BUG]  pruning:	grid:	add all snapshots that do not match the regex to
       the rule's destroy list.  This brings the implementation in line with the
       docs.

     * [BUG] easyrsa script in docs

     * [BUG] platformtest: fix skipping encryption-only tests  on  systems  that
       don't support encryption

     * [BUG] replication: report AttemptDone if no filesystems are replicated

     * [FEATURE] status + replication: warning if replication succeeeded without
       any filesystem being replicated

     * [DOCS] update multi-job & multi-host setup section

     * RPM Packaging

     * CI infrastructure rework

     * Continuous deployment of that new stable branch to zrepl.github.io.

   0.3
     This is a big one! Headlining features:

     * Resumable Send & Recv Support No knobs required, automatically used where
       supported.

     * Encrypted Send & Recv Support for OpenZFS native encryption, configurable
       at the job level, i.e., for all filesystems a job is responsible for.

     * Replication  Guarantees	Automatic use of ZFS holds and bookmarks to pro-
       tect a replicated filesystem from losing synchronization  between  sender
       and  receiver.  By default, zrepl guarantees that incremental replication
       will always be possible and interrupted steps will always be resumable.

     TIP:
	We highly recommend studying the updated overview section of the config-
	uration chapter to understand how replication works.

     TIP:
	Go 1.15 changed the default TLS validation policy to require Subject Al-
	ternative Names (SAN) in certificates.	The openssl commands we provided
	in the quick-start guides up to and including the zrepl  0.3  docs  seem
	not  to  work  properly.  If you encounter certificate validation errors
	regarding SAN and wish to continue to use your old  certificates,  start
	the  zrepl  daemon  with env var GODEBUG=x509ignoreCN=0.  Alternatively,
	generate new certificates with SANs (see both options int the TLS trans-
	port docs ).

     Quick-start guides:

     * We have added another quick-start guide for  a  typical	workstation  use
       case  for  zrepl.  Check it out to learn how you can use zrepl to back up
       your workstation's OpenZFS natively-encrypted root filesystem to  an  ex-
       ternal disk.

     Additional changelog:

     * [BREAK] Go 1.15 TLS changes mentioned above.

     * [BREAK]	[CONFIG] more restrictive job names than in prior zrepl versions
       Starting with this version, job names are going to be embedded  into  ZFS
       holds  and  bookmark names (see this section for details).  Therefore you
       might need to adjust your job names.  Note that jobs  cannot  be  renamed
       easily once you start using zrepl 0.3.

     * [BREAK] [MIGRATION] replication cursor representation changed

       * zrepl	now  manages  the replication cursor bookmark per job-filesystem
	 tuple instead of a single replication cursor per  filesystem.	 In  the
	 future,  this	will  permit multiple sending jobs to send from the same
	 filesystems.

       * ZFS does not allow bookmark renaming, thus we cannot  migrate	the  old
	 replication cursors.

       * zrepl	0.3  will automatically create cursors in the new format for new
	 replications, and warn if it still finds ones in the old format.

       * Run zrepl migrate replication-cursor:v1-v2 to safely destroy old-format
	 cursors.  The migration will ensure that only those old-format  cursors
	 are destroyed that have been superseeded by new-format cursors.

     * [FEATURE] New option listen_freebind (tcp, tls, prometheus listener)

     * [FEATURE]  issue #341 Prometheus metric for failing replications + corre-
       sponding Grafana panel

     * [FEATURE] issue #265 transport/tcp: support for CIDR masks in  client  IP
       whitelist

     * [FEATURE] documented subcommand to generate bash and zsh completions

     * [FEATURE] issue #307 chrome://trace -compatible activity tracing of zrepl
       daemon activity

     * [FEATURE]  logging:  trace IDs for better log entry correlation with con-
       current replication jobs

     * [FEATURE] experimental environment variable for parallel replication (see
       issue #306 )

     * [BUG] missing logger context vars in control connection handlers

     * [BUG] improved error messages on zfs send errors

     * [BUG] [DOCS]  snapshotting:  clarify  sync-up  behavior	and  warn  about
       filesystems

     * [BUG]  transport/ssh:  do not leak zombie ssh process on connection fail-
       ures that will not be snapshotted until the sync-up phase is over

     * [DOCS] Installation: FreeBSD jail with iocage

     * [DOCS] Document new replication features in the	config	overview  and  -
       replication/design.md.

     * [MAINTAINER  NOTICE] New platform tests in this version, please make sure
       you run them for your distro!

     * [MAINTAINER NOTICE] Please add the shell completions to the  zrepl  pack-
       ages.

   0.2.1
     * [FEATURE]  Illumos (and Solaris) compatibility and binary builds (thanks,
       MNX.io )

     * [FEATURE] 32bit binaries for Linux and FreeBSD (untested, though)

     * [BUG] better error messages in ssh+stdinserver transport

     * [BUG]	 systemd     +	   ssh+stdinserver:	automatically	  create
       /var/run/zrepl/stdinserver

     * [BUG] crash if Prometheus listening socket cannot be opened

     * [MAINTAINER NOTICE] Makefile refactoring, see commit 080f2c0

   0.2
     * [FEATURE]  Pre-	and  Post-Snapshot Hooks with built-in support for MySQL
       and  Postgres  checkpointing  as  well  as  custom  scripts  (thanks,   -
       @overhacked!)

     * [FEATURE]  Use  zfs destroy pool/fs@snap1,snap2,... CLI feature if avail-
       able

     * [FEATURE] Linux ARM64 Docker build support & binary builds

     * [FEATURE] zrepl status now displays snapshotting reports

     * [FEATURE] zrepl status --job <JOBNAME> filter flag

     * [BUG] i386 build

     * [BUG] early validation of host:port tuples in config

     * [BUG] zrepl status now supports TERM=screen (tmux on FreeBSD / FreeNAS)

     * [BUG] ignore connection reset by peer errors when shutting  down  connec-
       tions

     * [BUG] correct error messages when receive-side pool or root_fs dataset is
       not imported

     * [BUG] fail fast for misconfigured local transport

     * [BUG]  race  condition  in  replication report generation would crash the
       daemon when running zrepl status

     * [BUG] rpc goroutine leak in push mode if zfs recv fails on the sink side

     * [MAINTAINER NOTICE] Go modules for dependency management both inside  and
       outside of GOPATH (lazy.sh and Makefile force GO111MODULE=on)

     * [MAINTAINER  NOTICE]  make  platformtest  target to check zrepl's ZFS ab-
       stractions (screen scraping, etc.).  These tests only work  on  a  system
       with  ZFS  installed,  and  must  be  run  as  root because they create a
       file-backed pool for each test case.  The pool name zreplplatformtest  is
       reserved  for this use case.  Only run make platformtest on test systems,
       e.g. a FreeBSD VM image.

   0.1.1
     * [BUG] issue #162 commit d6304f4 : fix I/O timeout errors on variable  re-
       ceive rate

       * A  significant reduction or sudden stall of the receive rate (e.g. recv
	 pool has other I/O to do) would cause a writev I/O timeout error  after
	 approximately ten seconds.

   0.1
     This  release is a milestone for zrepl and required significant refactoring
     if not rewrites of substantial parts of the application.	It  breaks  both
     configuration  and  transport format, and thus requires manual intervention
     and updates on both sides of a replication setup.

     DANGER:
	The changes in the pruning system for this release require  you  to  ex-
	plicitly  define  keep rules: for any snapshot that you want to keep, at
	least one rule must match.  This is  different	from  previous	releases
	where  pruning	only affected snapshots with the configured snapshotting
	prefix.  Make sure that snapshots to be kept or  ignored  by  zrepl  are
	covered,  e.g.	by  using the regex keep rule.	Learn more in the config
	docs...

   Notes to Package Maintainers
     * Notify users about config changes and migrations (see changes  attributed
       with [BREAK] and [MIGRATION] below)

     * If  the	daemon	crashes,  the stack trace produced by the Go runtime and
       possibly diagnostic output of zrepl will be written to stderr.  This  be-
       havior  is independent from the stdout outlet type.  Please make sure the
       stderr output of the daemon is captured somewhere.  To conserve	precious
       stack  traces,  make  sure that multiple service restarts do not directly
       discard previous stderr output.

     * Make it obvious for users how to set the GOTRACEBACK environment variable
       to GOTRACEBACK=crash.  This functionality will cause  SIGABRT  on  panics
       and  can be used to capture a coredump of the panicking process.  To that
       extend, make sure that your package build system, your OS's coredump col-
       lection and the Go delve debugger work together.  Use your  build  system
       to  package the Go program in this tutorial on Go coredumps and the delve
       debugger , and make sure the symbol resolution  etc.  work  on  coredumps
       captured from the binary produced by your build system. (Special focus on
       symbol stripping, etc.)

     * Consider  using	the  zrepl  configcheck subcommand in startup scripts to
       abort a restart that would fail due to an invalid config.

   Changes
     * [BREAK] [MIGRATION] Placeholder property representation changed

       * The placeholder property now uses on|off as values instead of hashes of
	 the dataset path. This permits renames of the sink  filesystem  without
	 updating all placeholder properties.

       * Relevant for 0.0.X-0.1-rc* to 0.1 migrations

       * Make sure your config is valid with zrepl configcheck

       * Run zrepl migrate 0.0.X:0.1:placeholder

     * [FEATURE] issue #55 : Push replication (see push job and sink job)

     * [FEATURE] TCP Transport

     * [FEATURE] TCP + TLS client authentication transport

     * [FEATURE] issue #111: RPC protocol rewrite

       * [BREAK]  Protocol  breakage; Update and restart of all zrepl daemons is
	 required.

       * Use gRPC for control RPCs and a custom protocol for bulk data transfer.

       * Automatic retries for network-temporary errors

	 * Limited to errors during replication for this release.  Addresses the
	   common problem of ISP-forced reconnection at night, but  will  become
	   way	more  useful with resumable send & recv support.  Pruning errors
	   are handled per FS, i.e., a prune RPC is attempted at least once  per
	   FS.

     * [FEATURE] Proper timeout handling for the SSH transport

       * [BREAK] Requires Go 1.11 or later.

     * [BREAK] [CONFIG]: mappings are no longer supported

       * Receiving sides (pull and sink job) specify a single root_fs.	Received
	 filesystems  are  then  stored  per client in ${root_fs}/${client_iden-
	 tity}.  See Jobs & How They Work Together for details.

     * [FEATURE] [BREAK] [CONFIG] Manual snapshotting + triggering  of	replica-
       tion

       * [FEATURE] issue #69: include manually created snapshots in replication

       * [CONFIG] manual and periodic snapshotting types

       * [FEATURE]  zrepl  signal wakeup JOB subcommand to trigger replication +
	 pruning

       * [FEATURE] zrepl signal reset JOB subcommand to abort  current	replica-
	 tion + pruning

     * [FEATURE] [BREAK] [CONFIG] New pruning system

       * The  active  side of a replication (pull or push) decides what to prune
	 for both sender and receiver.	The RPC protocol is used to execute  the
	 destroy operations on the remote side.

       * New pruning policies (see configuration documentation )

	 * The decision what snapshots shall be pruned is now made based on keep
	   rules

	 * [FEATURE]  issue #68: keep rule not_replicated prevents divergence of
	   sender and receiver

       * [FEATURE] [BREAK] Bookmark pruning is no longer necessary

	 * Per filesystem, zrepl  creates  a  single  bookmark	(#zrepl_replica-
	   tion_cursor)  and  moves it forward with the most recent successfully
	   replicated snapshot on the receiving side.

	 * Old bookmarks created by prior versions of zrepl  (named  like  their
	   corresponding snapshot) must be deleted manually.

	 * [CONFIG]  keep_bookmarks parameter of the grid keep rule has been re-
	   moved

     * [FEATURE] zrepl status for live-updating replication progress  (it's  re-
       ally cool!)

     * [FEATURE]  Snapshot-  & pruning-only job type (for local snapshot manage-
       ment)

     * [FEATURE] issue #67: Expose Prometheus metrics via HTTP (config docs)

       * Compatible Grafana dashboard shipping in dist/grafana

     * [CONFIG] Logging outlet types must be specified using the type instead of
       outlet key

     * [BREAK] issue #53: CLI: zrepl control * subcommands have been made direct
       subcommands of zrepl *

     * [BUG] Goroutine leak on ssh transport connection timeouts

     * [BUG] issue #81 issue #77 : handle failed accepts correctly (source job)

     * [BUG] issue #100: fix incompatibility with ZoL 0.8

     * [FEATURE] issue #115: logging: configurable syslog facility

     * [FEATURE] Systemd unit file in dist/systemd

   Previous Releases
     NOTE:
	Due to limitations  in	our  documentation  system,  we  only  show  the
	changelog  since  the  last  release  and the time this documentation is
	built.	For the changelog of previous releases, use the  version  selec-
	tion in the hosted version of these docs at zrepl.github.io.

   Future
     This  page  contains  some notes about future plans for zrepl.  Then again,
     Christian only has very limited time available for zrepl maintenance  these
     days.  So, don't count on any of this happening in the near future.

     One  big  development  that  has  happened  in recent years is this fork of
     zrepl: https://github.com/dsh2dsh/zrepl We should figure out whether we can
     pull in features from there.

     The next major step in terms of new feature development for zrepl would  be
     to revise snapshot management:

     * Make it easy to decouple snapshot management (snapshotting, pruning) from
       replication.

     * Ability	to  include/exclude  snapshots from replication.  This is useful
       for aforementioned decoupling, e.g., separate snapshot prefixes for local
       & remote replication.  Also, it makes explicit  that  by  default,  zrepl
       replicates  all	snapshots,  and  that  replication  has  no  concept  of
       "zrepl-created snapshots", which is a common misconception.

     * Use of zfs snapshot comma syntax or channel programs to take snapshots of
       multiple datasets atomically.

     * Provide an alternative to the grid pruning policy.  Most likely something
       based on hourly/daily/weekly/monthly "trains" plus a count.

     * Ability to prune at the granularity of the group of snapshots created  at
       a  given  time,	as opposed to the individual snapshots within a dataset.
       Maybe this will be addressed by the alternative to the grid pruning  pol-
       icy, as it will likely be more predictable.

     Those  changes will likely come with some breakage in the config.	However,
     I want to avoid breaking use cases that are satisfied by  the  current  de-
     sign.   There  will be beta/RC releases to give users a chance to evaluate.
     Donate via Patreon Donate via GitHub Sponsors Donate via  Liberapay  Donate
     via PayPal

     zrepl  is	a  spare-time  project primarily developed by Christian Schwarz.
     You can support maintenance and feature development through one of the ser-
     vices listed above.  For SEPA wire transfer and commercial support,  please
     contact Christian directly.

     Thanks for your support!

     NOTE:
	PayPal	takes  a relatively high fixed processing fee plus percentage of
	the donation.  Larger less-frequent donations make more sense there.

   Supporters
     We would like to thank the following people and organizations for	support-
     ing zrepl through monetary and other means:

     *
	Hostsharing eG ^a die Hosting-Genossenschaft

     *
	Max Christian Pohle

     *
	Prominic.NET, Inc.

     *
	Torsten Blum

     *
	Cyberiada GmbH

     *
	Gordon Schulz

     *
	@jwittlincohen

     *
	Michael D. Schmitt

     *
	Hans Schulz

     *
	Henning Kessler

     *
	John Ramsden

     *
	DrLuke

     *
	Mateusz Kwiatkowski (runhyve.app)

     *
	Gaelan D'costa

     *
	Tenzin Lhakhang

     *
	Lapo Luchini

     *
	F. Schmid

     *
	MNX.io

     *
	Marshall Clyburn

     *
	Ross Williams

     *
	Mike T.

     *
	Justin Scholz

     *
	InsanePrawn

     *
	Ben Woods

     *
	Janis Streib

     *
	Anton Schirg

AUTHOR
     Christian Schwarz

COPYRIGHT
     2017-2026, Christian Schwarz

latest				  Aug 02, 2026				ZREPL(1)

Want to link to this manual page? Use this URL:
<https://man.freebsd.org/cgi/man.cgi?query=zrepl&manpath=FreeBSD+15.1-RELEASE+and+Ports.quarterly>

home | help