Skip to content

Instantly share code, notes, and snippets.

@gboudreau
Last active May 19, 2018 02:43
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save gboudreau/91b8866e3adb19965897ecd80a12525b to your computer and use it in GitHub Desktop.
Save gboudreau/91b8866e3adb19965897ecd80a12525b to your computer and use it in GitHub Desktop.
duplicacy monitoring of incoming backups - email notifications when 3d / 7d without backup
#!/usr/bin/php
<?php
/*
Instructions:
- Download this script from : https://gist.githubusercontent.com/gboudreau/91b8866e3adb19965897ecd80a12525b/raw/duplicacy-monitoring.php
- Make it executable: chmod +x duplicacy-monitoring.php
- Obtain the Duplicacy CLI from https://github.com/gilbertchen/duplicacy/releases
- Configure the script by modifying the variables below.
- Create an empty folder to use as 'duplicacy_monitoring_path'
- Test the script manually: /path/to/duplicacy-monitoring.php
This will allow you to enter the encryption password for the backups, if any.
- Schedule it using cron:
0 9 * * * /path/to/duplicacy-monitoring.php
*/
// Path to the duplicacy executable
$duplicacy_exec = '/usr/local/bin/duplicacy';
// Where backups are stored (there is a 'snapshots' folder in there)
$backup_paths = [
"/mnt/hdd5/backups/duplicacy/Guillaumes-MacBook-Pro-gb-gb",
];
// An empty folder that will be used to setup local repositories used stricly for monitoring; it needs to already exist.
// !!! If you use encryption, make sure this folder is not accessible to others, as it will contain your backup password(s) !!!
$duplicacy_monitoring_path = '/home/you/duplicacy-monitoring';
// Send email notifications to this address, when there has been no backups for X days
$sysdmin_email = 'you@test.com';
// No backups since 3 days = 1st notif; no backups since 7 days = 2nd notif
// You can use d (days) or h (hours) suffixes; notifications will be sent when the last backup was last made between X and X+1 (days or hours), so if you use days here, schedule the cron job to run once a day, otherwise, run it every hour.
$notifications = [
'3d' => 'Warning',
'7d' => 'Error'
];
// Configuration ends here!
$hostname = exec('hostname -f');
$duplicacy_monitoring_path = '/' . trim($duplicacy_monitoring_path, '/');
foreach ($backup_paths as $k => $backup_path) {
$backup_paths[$k] = '/' . trim($backup_path, '/');
if (!file_exists("$backup_path/snapshots/")) {
die("Error: missing 'snapshots' folder in backup_bath '$backup_path'. Exiting.\n");
}
if ($duplicacy_monitoring_path == $backup_path || strpos($duplicacy_monitoring_path, "$backup_path/") !== FALSE) {
die("Error: 'duplicacy_monitoring_path' ($duplicacy_monitoring_path) can't be inside 'backup_path' ($backup_path). Exiting.\n");
}
}
$report_date = date('Y-m-d H:i');
foreach ($backup_paths as $backup_path) {
foreach (glob("$backup_path/snapshots/*") as $snapshot_path) {
$snapshot_id = basename($snapshot_path);
if (!file_exists("$duplicacy_monitoring_path/$snapshot_id")) {
mkdir("$duplicacy_monitoring_path/$snapshot_id");
chdir("$duplicacy_monitoring_path/$snapshot_id");
chmod("$duplicacy_monitoring_path/$snapshot_id", 0700);
do {
$encrypted = strtolower(readline("Is the backup for '$snapshot_id' encrypted? (y/n) "));
} while (array_search($encrypted, ['y', 'n']) === FALSE);
$extra_params = '';
if ($encrypted === 'y') {
$extra_params = '-e ';
}
passthru(escapeshellarg($duplicacy_exec) . ' init ' . $extra_params . escapeshellarg($snapshot_id) . ' ' . escapeshellarg($backup_path));
chmod("$duplicacy_monitoring_path/$snapshot_id/.duplicacy", 0700);
chmod("$duplicacy_monitoring_path/$snapshot_id/.duplicacy/preferences", 0600);
if ($encrypted === 'y') {
$password = readline("Enter storage password (again): ");
exec(escapeshellarg($duplicacy_exec) . " set -key password -value " . escapeshellarg($password));
_log("!!! WARNING !!! Password stored in $duplicacy_monitoring_path/$snapshot_id/.duplicacy/preferences ; make sure nobody can read this file !!!");
}
}
chdir("$duplicacy_monitoring_path/$snapshot_id");
$last_backup = exec(escapeshellarg($duplicacy_exec) . ' list | grep -v "Storage set to " | tail -1');
if (empty($last_backup) || $last_backup == "Repository has not been initialized") {
_log("No backup (snapshot) found for id '$snapshot_id'; i.e. the first backup didn't complete yet.");
} else {
if (!preg_match("/Snapshot (.+) revision ([\d]+) created at (\d+-\d+-\d+ \d+:\d+)/", $last_backup, $re)) {
_log("ERROR! Couldn't parse output from 'duplicacy list': $last_backup\n");
continue;
}
$found_snapshot_id = $re[1];
$revision_number = $re[2];
$last_backup_date = $re[3];
$seconds_since_last_backup = time() - strtotime($last_backup_date);
$since_last_backup_h = $seconds_since_last_backup / 3600;
$since_last_backup_d = $since_last_backup_h / 24;
_log("Last backup from $snapshot_id was " . round($since_last_backup_h) . " hours (" . round($since_last_backup_d, 1) . " days) ago...");
foreach ($notifications as $notif => $level) {
if (!preg_match('/^(\d+)([dh])$/', $notif, $re)) {
_log("WARNING; wrong 'notification' definition: $notif. Expected format: '#d' or '#h'. Skipping.");
continue;
}
$trigger_number = $re[1];
$trigger_unit = $re[2];
if (${"since_last_backup_$trigger_unit"} < $trigger_number) {
_log(" OK.");
break;
} else {
_log(" Oh noes! Backup from '$snapshot_id' is missing for more than $notif!!");
if (${"since_last_backup_$trigger_unit"} < $trigger_number+1) {
_log(" Sending email notification to $sysdmin_email");
mail($sysdmin_email, "[$level] Backup missing for $snapshot_id !", "No duplicacy backup was made in the last $notif from $snapshot_id to $hostname.\nLast backup found: $last_backup\n");
break;
} else {
_log(" Not sending another email notification; was already sent not so long ago.");
}
}
}
}
}
}
function _log($log) {
global $report_date;
error_log("[$report_date] $log");
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment