Files

251 lines
8.6 KiB
Markdown

# 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.
- Both hosts need `rsync` to run the verification script.
- The SSH account on the destination must be able to execute `sudo -n zfs`
without a password prompt.
- For verification, that account must also be allowed to run `sudo -n rsync`
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
```
Verify the resulting filesystems after mounting them on both hosts:
```bash
bash zfs-verify-over-ssh.sh tank root@new-server backup/tank
```
The verifier maps datasets using the same relative paths as the move script.
It uses a read-only `rsync --dry-run --checksum` comparison and exits with
status `1` when it finds missing, extra, changed, or metadata-different files.
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
```
## Verification
`zfs-verify-over-ssh.sh` compares each ZFS filesystem below `SOURCE_POOL` to
the corresponding filesystem below `DESTINATION_ROOT`. For example,
`tank/data/documents` is compared with `backup/tank/data/documents`.
```bash
bash zfs-verify-over-ssh.sh [options] SOURCE_POOL REMOTE_HOST DESTINATION_ROOT
```
Both sides of every filesystem pair must be mounted. The transfer script uses
`zfs receive -u`, so mount the received remote datasets before verification:
```bash
ssh admin@new-server 'sudo zfs mount -a'
bash zfs-verify-over-ssh.sh tank admin@new-server backup/tank
```
The verifier uses `rsync` with `--dry-run`, `--checksum`, `--delete`, archive
metadata preservation, hard-link checking, ACL checking, extended-attribute
checking, and `--one-file-system`. It does not modify either host. Any
itemized `rsync` output identifies an extra, missing, changed, or
metadata-different path.
The verifier continues with the next filesystem when one dataset cannot be
checked. It prints an `ERROR` line for each failed dataset and summarizes the
number of uncheckable filesystems at the end, so one run shows all failures
encountered along the way.
Verification options:
| Option | Description |
| --- | --- |
| `-i`, `--identity-file FILE` | Use the specified SSH private key. |
| `-p`, `--ssh-port PORT` | Connect to the specified SSH port. |
| `-h`, `--help` | Show command help. |
Use the same key and port options as the transfer when required:
```bash
bash zfs-verify-over-ssh.sh \
--identity-file ~/.ssh/zfs-migration \
--ssh-port 2222 \
tank admin@new-server backup/tank
```
Exit statuses:
| Status | Meaning |
| --- | --- |
| `0` | All checked filesystems are identical. |
| `1` | All filesystem pairs were checked, but one or more have differences. |
| `2` | One or more filesystem pairs could not be checked, for example because a dataset is unavailable or unmounted, or its SSH, sudo, or `rsync` comparison failed. The script continues and lists these errors before exiting. |
For a stable result, stop writes to the source and destination while the
verifier runs, or verify mounted clones of the same snapshots. The verifier
checks file content and selected filesystem metadata; it does not compare ZFS
dataset properties, snapshots, quotas, reservations, or encryption settings.
## 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.
Use the exact same account, hostname, key, and port in the script as in this
successful test. For example, if `admin@new-server` succeeds, run:
```bash
bash zfs-move-over-ssh.sh \
-i ~/.ssh/zfs-migration \
tank \
admin@new-server \
backup/tank
```
Do not substitute `root@new-server` for `admin@new-server` unless a separate
non-interactive key test succeeds for `root`. SSH keys and `sudo` permissions
are configured per remote user, so a key that works for `admin` may be denied
for `root`.
`REMOTE_HOST` must be a complete hostname or address, such as
`root@192.0.2.10` or `admin@new-server`. `root@` is invalid because it has no
host after the `@`.
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. Mount the received datasets, then verify their data before retiring the
source. `zfs-move-over-ssh.sh` receives with `-u`, so the destination filesystems
are unmounted until you explicitly mount them:
```bash
ssh admin@new-server 'sudo zfs mount -a'
bash zfs-verify-over-ssh.sh tank admin@new-server 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.
- Before each receive, the script checks whether the remote destination already
has the matching `move-YYYYMMDD-HHMMSS` snapshot. If it does, the script asks
whether to delete that specific remote snapshot and retry the transfer. Type
`y` only after confirming it is an incomplete or disposable migration
snapshot; any other response stops the script without deleting it.
- 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`.