# ZFS Move Over SSH `zfs-move-over-ssh.sh` snapshots every ZFS filesystem in a source pool, then sends each snapshot to a remote ZFS server over SSH. A vdev is the underlying storage device; ZFS filesystems belong to a pool, so pass the pool name. For a source tree such as: ```text tank/data tank/data/documents tank/data/media ``` and destination root `backup/data`, the script sends the filesystems to: ```text backup/data backup/data/documents backup/data/media ``` Each local snapshot is named `move-YYYYMMDD-HHMMSS`. ## Requirements - Run the script as an account that can use `zfs` on the source server. - The source host needs Bash, ZFS utilities, and SSH. - The destination host needs ZFS utilities and the destination pool must exist. - The SSH account on the destination must be able to execute `sudo -n zfs` without a password prompt. - SSH must use a private key or SSH agent. Password authentication is disabled by the script so a migration cannot pause and prompt once per dataset. - SSH connectivity and host-key verification must already work. The script is intended for one-shot full snapshot transfers. It does not use incremental sends or resume tokens. ## Usage ```bash bash zfs-move-over-ssh.sh [options] SOURCE_POOL REMOTE_HOST DESTINATION_ROOT ``` Example: ```bash bash zfs-move-over-ssh.sh tank root@new-server backup/tank ``` Use a non-standard SSH key or port when needed: ```bash bash zfs-move-over-ssh.sh \ --identity-file ~/.ssh/zfs-migration \ --ssh-port 2222 \ tank admin@new-server backup/tank ``` ## SSH Setup Create a dedicated key on the source server if one does not already exist: ```bash ssh-keygen -t ed25519 -f ~/.ssh/zfs-migration -C zfs-migration ``` Install its public key for the remote account. This one command may ask for the remote account password during setup; the migration itself will not: ```bash ssh-copy-id -i ~/.ssh/zfs-migration.pub admin@new-server ``` Verify that authentication and remote ZFS access work without either password: ```bash ssh -i ~/.ssh/zfs-migration -o BatchMode=yes admin@new-server \ 'sudo -n zfs list' ``` If this command fails, configure the remote account's `sudoers` entry for the necessary `zfs` commands with `NOPASSWD` before running the migration. Do not put a password in the script or on its command line. Options: | Option | Description | | --- | --- | | `-n`, `--dry-run` | Print planned snapshot, send, and receive commands without running them. | | `-i`, `--identity-file FILE` | Use the specified SSH private key. | | `-p`, `--ssh-port PORT` | Connect to the specified SSH port. | | `--destroy-source-snapshots` | Remove each local migration snapshot only after its receive succeeds. | | `-h`, `--help` | Show command help. | ## Recommended Procedure 1. Confirm the source datasets: ```bash zfs list -r tank ``` 2. Confirm remote privileged ZFS access: ```bash ssh admin@new-server 'sudo -n zfs list' ``` 3. Preview the operation. Review every destination name in the output: ```bash bash zfs-move-over-ssh.sh --dry-run tank admin@new-server backup/tank ``` 4. Run the migration: ```bash bash zfs-move-over-ssh.sh tank admin@new-server backup/tank ``` 5. Verify the received datasets and data before retiring the source: ```bash ssh admin@new-server 'sudo zfs list -r backup/tank' ``` ## Safety and Recovery - The script does **not** destroy source filesystems or source data. - By default, the generated local snapshots remain on the source. This allows a failed run to be inspected or sent again. Remove them manually only after verification, or use `--destroy-source-snapshots` once the workflow is trusted. - The remote command is `zfs receive -uF`. `-u` leaves received filesystems unmounted. `-F` can roll back the destination filesystem and destroy newer snapshots or changes that conflict with the incoming snapshot. Do not point the script at a destination containing data you need unless you have reviewed the dry-run output and have a current backup. - The script processes one filesystem at a time. If it stops partway through, completed destination filesystems remain received. Re-running with the same source can create another timestamped snapshot; inspect both source and destination state before retrying. - A successful transfer is not a destructive move. Verify applications and data on the destination, then perform any source cleanup separately. ## Notes The destination root itself does not have to exist before the transfer, but its parent pool or filesystem must exist and the remote account must have ZFS permission to create the target datasets. Dataset properties are included in the stream through `zfs send -p`.