192 lines
6.4 KiB
Markdown
192 lines
6.4 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
|
|
```
|
|
|
|
## 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`. |