Skip to content

Instantly share code, notes, and snippets.

@geek-at
Created February 28, 2022 19:21
Show Gist options
  • Save geek-at/b27a25735bf2045856c8df3ae116911d to your computer and use it in GitHub Desktop.
Save geek-at/b27a25735bf2045856c8df3ae116911d to your computer and use it in GitHub Desktop.
PHP script that syncs remote ZFS snapshots to local dataset. Works with encrypted volumes
<?php
/*
Script that syncs remote zfs snapshots to a local zfs target.
Can sync remote encrypted datasets without ever needing to know the key on the local machine.
It will automatically search for the last snapshot in common with the remote host and only sync the differences.
If it doesn't find any local ones or has none in common, it will sync the oldest snapshot of the remote machine.
Meant to be run as cronjob
Prerequisites:
- ZFS on remote and local machine
- Passwordless SSH login from local to remote machine
-
Usage:
php zfs_sync.php <remote ip or hostname> <remote dataset> <local dataset>
eg. If I have a server called "green" and the remote dataset is data/pictures
we can sync to a local pool called backup/pictures using
php zfs_sync.php green data/pictures backup/pictures
*/
$remoteIP = $argv[1];
$remoteDataset = $argv[2];
$localDataset = $argv[3];
if($argc!=4) exit('usage: php sync.php <remote ip> <remote dataset> <local dataset>'.PHP_EOL);
$tmplock = '/tmp/'.md5($remoteIP.$remoteDataset.$localDataset);
echo "[i] ======== SYNC START FROM $remoteIP: $remoteDataset to $( hostname ): $localDataset =========\n";
if(file_exists($tmplock))
exit('Lockfile exists. Sync already running?');
touch($tmplock);
$cmd_zfs = "zfs list -t snapshot $remoteDataset | cut -d ' ' -f 1 | cut -d '@' -f 2 | tail -n +2";
$cmd_zfs_local = "zfs list -t snapshot $localDataset | cut -d ' ' -f 1 | cut -d '@' -f 2 | tail -n +2";
$cmd_ssh = "ssh $remoteIP $cmd_zfs";
$remotesnapshots = [];
exec($cmd_ssh,$remotesnapshots);
$localsnapshots = [];
exec($cmd_zfs_local,$localsnapshots);
$tosync = array_diff($remotesnapshots,$localsnapshots);
if(count($tosync)<1) exit("[i] Nothing to sync. Exiting\n".unlink($tmplock));
$snapshot = end($tosync);
$lastincommon = getLastInCommon($remotesnapshots,$localsnapshots,$snapshot);
if(!$lastincommon)
{
echo "[i] Don't have any snapshots of that dataset yet. I'll sync initial\n";
$firstsnap = $remotesnapshots[0];
$synccmd = "ssh $remoteIP zfs send -w $remoteDataset@$firstsnap | zfs receive -F $localDataset";
echo "$synccmd\n";
system($synccmd);
unlink($tmplock);
exit("[i] ======== SYNC END ========\n");
}
echo "[i] Syncing from $lastincommon to $snapshot\n";
$synccmd = "ssh $remoteIP zfs send -w -I $remoteDataset@$lastincommon $remoteDataset@$snapshot | zfs receive -F $localDataset";
echo "$synccmd\n";
//run the actual zfs command
system($synccmd);
//remove the lock file
unlink($tmplock);
echo "[i] ======== SYNC END ========\n";
function getLastInCommon($remote,$local,$target)
{
$incommon = [];
foreach($remote as $key=>$rsnap)
{
foreach($local as $lkey=>$lsnap)
{
if($rsnap == $lsnap)
$incommon[] = $rsnap;
if($lsnap==$target) break;
}
}
return end($incommon);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment