This repository has been archived by the owner on Feb 24, 2022. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Initial commit of readynas-backup scripts.
- Loading branch information
Robert Pufky
committed
Dec 14, 2015
1 parent
13f0cb8
commit 7f26dc8
Showing
5 changed files
with
985 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
Copyright 2008, Robert Pufky (github.com/r-pufky) | ||
|
||
ReadyNAS Backup Scripts | ||
----------------------- | ||
These were a collection of scripts I wrote to overcome the restricted backup | ||
functionality provided by the backup web-face on the ReadyNAS. Any questions or | ||
comments, please let me know! More information, and updated versions can be | ||
found at: | ||
|
||
http://www.crazymonkies.com/projects.php?type=readynas | ||
|
||
1) If you have never run cronjob tasks before, or not comfortable with using the | ||
commandline, STOP NOW. These scripts are fore more advanced users who want | ||
more control over their backups. | ||
|
||
2) Read the scripts and the cronjob file and verify they are setup the way you | ||
want them. Please note the generation of public key authentication for the | ||
NAS, and also note that as of this release, the ReadyNAS was affected by the | ||
openssl bug. If you don't want to use a script, you can just remove it from | ||
the cronjobs file, and delete the script. | ||
|
||
3) Prep the ReadyNAS for backup scripts: | ||
|
||
- Install SSH Addons if they are not installed already. They are located | ||
here: | ||
|
||
http://www.readynas.com/?page_id=93 | ||
|
||
You want to select your current ReadyNAS firmware version, then download | ||
and install the following packages: | ||
|
||
ToggleSSH | ||
EnableRootSSH | ||
|
||
- Create a backup share, only allowing READ access to normal users. I named | ||
my backup share "Backup" | ||
|
||
4) Copy the script to the root directory on your NAS; the command would be | ||
something like the following: | ||
|
||
scp -r ReadyNAS-Backup-Scripts/ root@YOURNAS:/root/ | ||
|
||
5) SSH to your box to verify the setup. I would recommend logging into your NAS | ||
and verifying your scripts are working as intended by first running the | ||
scripts with the --check and --log options: | ||
|
||
./[script]_backup --check --log | ||
|
||
After that, run the initial backup with that script. Please note that the | ||
first actual backup of data will take the longest. You should probably run | ||
the first backup with --log to make sure everything goes well: | ||
|
||
./[script]_backup --log | ||
|
||
6) Install your cronjob, and go. | ||
|
||
crontab /root/cronjobs |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,322 @@ | ||
#!/usr/bin/perl -w | ||
# | ||
# Sync's critical data to user read-only directory on NAS. | ||
# Copyright 2008, Robert Pufky (github.com/r-pufky) | ||
# http://www.crazymonkies.com/projects.php?type=readynas | ||
# | ||
# Installation / Usage: | ||
# --------------------- | ||
# run './critical_backup --help' for more information | ||
# | ||
# Recommended cronjob: | ||
# 30 23 * * * /root/backup_scripts/critical_backup | ||
# | ||
# Notes: | ||
# This is meant to be run on very small critical pieces of data, which will be | ||
# backed up very fast. If you want to backup larger sets of data, use the | ||
# regular_backup script, which can be run in a separate cronjob. Remote data | ||
# is backed up VIA rsync over SSH. | ||
# | ||
# The rsync over SSH setup that is used in this script requires public-key | ||
# authentication, which a basic setup is described below. Google for more | ||
# information. | ||
# | ||
# This job should run relatively quickly after the initial sync, as critical | ||
# data shouldn't change that much. Should be run relatively frequently. | ||
# | ||
# If NO directories are being backed up locally or remotely, just leave the | ||
# directory list blank. It should look like: | ||
# - local NAS directories: $NAS{'LOCAL'} = []; | ||
# - remote directories: $NAS{'REMOTE'} = []; | ||
# | ||
# Initially, this should be run manually with logging enabled to make sure you | ||
# are grabbing everything that you want. The initial sync will take the | ||
# longest, and it's best to monitor the first sync to adjust your backups | ||
# accordingly. | ||
# | ||
# ./critical_backup --log | ||
# | ||
# Public-key Setup: | ||
# - Setup local (NAS) .ssh directory | ||
# ssh root@YOURNAS | ||
# mkdir -p ${HOME}/.ssh | ||
# chmod 0700 ${HOME}/.ssh | ||
# - Generate a passwordless DSA public/private keyset ** | ||
# ssh-keygen -t dsa -f ${HOME}/.ssh/id_dsa -P '' | ||
# - Upload public key to your server VIA scp | ||
# scp -P PORT ${HOME}/.ssh/id_dsa.pub USER@SERVER:/REMOTEHOME/ | ||
# - Setup remote system directory | ||
# ssh USER@HOST -p PORT | ||
# mkdir -p ${HOME}/.ssh | ||
# cat ${HOME}/id_dsa.pub >> ${HOME}/.ssh/authorized_keys | ||
# cat ${HOME}/id_dsa.pub >> ${HOME}/.ssh/authorized_keys2 | ||
# chmod 0600 ${HOME}/.ssh/authorized_keys* | ||
# | ||
# ** At the time of this script, there was an openssl bug that affected the | ||
# ReadyNAS, which created cryptographically weak keys. The work-around is | ||
# to generate the keys on a Mac or linux box that isn't affected by the keys | ||
# using the folloing command, then copy the resulting (id_dsa,id_dsa.pub) | ||
# keys to your ReadNAS: | ||
# | ||
# ssh-keygen -t dsa -f ~/id_dsa -C 'root@YOURNAS' -P '' | ||
# scp -P PORT ~/id_dsa* root@YOURNAS:/root/.ssh/ | ||
# | ||
use strict; | ||
my %NAS; | ||
# ----------------------------------------------------------------------------- | ||
# Critical local NAS directories to backup | ||
$NAS{'LOCAL'} = [ | ||
'/me_home/Pastor/', | ||
'/me_home/Documents']; | ||
# Critical remote system directories to backup | ||
$NAS{'REMOTE'} = [ | ||
'/Users/me/Library/Keychains/', | ||
'/Users/me/Library/Calendars', | ||
'/Users/me/Library/iTunes', | ||
'/Users/me/Library/Application Support/AddressBook', | ||
'/Users/me/Library/Application Support/Little Snitch']; | ||
# Local NAS destination backup directory, should be from root of drive | ||
$NAS{'DESTINATION'} = '/Backup/Critical/'; | ||
# remote system to connect to | ||
$NAS{'HOST'} = '192.168.0.10'; | ||
# remote user to authenticate with | ||
$NAS{'USER'} = 'me'; | ||
# remote port for SSH server | ||
$NAS{'PORT'} = '22'; | ||
# E-mail message settings | ||
$NAS{'TO'} = 'readynas@example.com'; | ||
$NAS{'FROM'} = 'readynas@example.com'; | ||
# rsync binary location (run 'which rsync' from the command line to find it) | ||
$NAS{'RSYNC'} = '/usr/bin/rsync'; | ||
# ----------------------------------------------------------------------------- | ||
|
||
|
||
|
||
my $start_time = time(); | ||
my $check = 0; | ||
my $escape_spaces = 1; | ||
my $log = 0; | ||
foreach(@ARGV) { | ||
if( $_ eq '--check' ) { $check = 1; } | ||
if( $_ eq '--log' ) { $log = 1; } | ||
if( $_ eq '--no-space-escape' ) { $escape_spaces = 0; } | ||
if( $_ eq '--help' or $_ eq '-h' ) { usage(); exit(0); } | ||
} | ||
%NAS = clean_options(\%NAS, $check, $escape_spaces, $log); | ||
if( !-d $NAS{'DESTINATION'} && !-w $NAS{'DESTINATION'} ) { | ||
my $error = 'Local NAS directory '.$NAS{'DESTINATION'}.' not found, not a '. | ||
'directory or not writable by this program!'; | ||
if( $check ) { print "\n".$error."\n";} | ||
send_mail($NAS{'TO'},$NAS{'FROM'},"Critical backup FAILED.",$error); | ||
} else { | ||
send_mail($NAS{'TO'},$NAS{'FROM'},"Starting backup of critical data...", | ||
"All options verified,\n\nStarting critical backup now..."); | ||
%NAS = backup(\%NAS, $check); | ||
my $body = "Successful backups:\n-------------------\n" . | ||
join("\n",@{$NAS{'successes'}}); | ||
if( scalar(@{$NAS{'failures'}}) == 0 ) { | ||
$body .= "\n\nThere were no failed backup jobs."; | ||
} else { | ||
$body .= "\n\nFailed backups:\n---------------\n" . | ||
join("\n",@{$NAS{'failures'}}); | ||
} | ||
my ($secs,$mins,$hours) = gmtime(time() - $start_time); | ||
$body .= "\n\nJob completed in ".$hours." hours, ". | ||
$mins." minutes, ".$secs." seconds."; | ||
if( scalar(@{$NAS{'failures'}}) == 0 ) { | ||
send_mail($NAS{'TO'},$NAS{'FROM'},"Critical backup success!!",$body); | ||
} else { | ||
send_mail($NAS{'TO'},$NAS{'FROM'},"Critical backup FAILED.",$body); | ||
} | ||
} | ||
|
||
# Function: clean_options | ||
# Purpose: cleans initial options and sets additional key options: | ||
# RSYNC_OPTIONS, RSYNC_REMOTE_OPTIONS, failures, successes | ||
# Requires: NAS - a hash reference containing the configuration keys | ||
# check - boolean, True to run rsync in dry-run mode | ||
# escape_spaces - boolean, True to escape destination string | ||
# log - boolean, True to ryn rsync in verbose mode | ||
# Returns: A NAS hash with new and cleaned keys | ||
sub clean_options { | ||
# grab the NAS reference, and create a nice hash pointer to the NAS data | ||
my($NAS_reference, $check, $escape_spaces, $log) = @_; | ||
my %NAS = %{$NAS_reference}; | ||
|
||
$NAS{'RSYNC_OPTIONS'} = | ||
'--size-only --copy-unsafe-links --archive --delete'; | ||
if( $check ) { $NAS{'RSYNC_OPTIONS'} .= ' --dry-run'; } | ||
if( $log ) { $NAS{'RSYNC_OPTIONS'} .= ' --verbose'; } | ||
|
||
# ensure trailing / on destination directory | ||
$NAS{'DESTINATION'} =~ s/(.*)([^\/]$)/$1$2\//; | ||
|
||
# remove trailing / on local directories, ensure no duplicate 'source' dirs | ||
foreach(@{$NAS{'LOCAL'}}) { $_ =~ s/\/$//; } | ||
check_duplicates(\@{$NAS{'LOCAL'}}); | ||
|
||
# remote trailing /, escape spaces if specified, ensure no duplicate 'remote' | ||
# directories | ||
foreach(@{$NAS{'REMOTE'}}) { | ||
if( $escape_spaces ) { $_ =~ s/ /\\ /g; } | ||
$_ =~ s/\/$//; | ||
} | ||
check_duplicates(\@{$NAS{'REMOTE'}}); | ||
|
||
$NAS{'RSYNC_REMOTE_OPTIONS'} = | ||
'--rsh="ssh -p '.$NAS{'PORT'}.'" '.$NAS{'USER'}.'@'.$NAS{'HOST'}.':'; | ||
$NAS{'failures'} = []; | ||
$NAS{'successes'} = []; | ||
|
||
if( $check ) { | ||
print "\nCleaned options:\n----------------\n"; | ||
print "Local backup directories:\n"; | ||
print join("\n",@{$NAS{'LOCAL'}}); | ||
print "\n\nRemote backup directories:\n"; | ||
print join("\n",@{$NAS{'REMOTE'}}); | ||
print "\n\nEscape spaces in remote directories?: "; | ||
if( $escape_spaces == 1 ) { | ||
print "YES\n"; | ||
} else { | ||
print "NO\n"; | ||
} | ||
print "NAS destination directory: " . $NAS{'DESTINATION'} . "\n"; | ||
print "Remote host: " . $NAS{'HOST'} . "\n"; | ||
print "Remote user: " . $NAS{'USER'} . "\n"; | ||
print "Remote port: " . $NAS{'PORT'} . "\n"; | ||
print "To address: " . $NAS{'TO'} . "\n"; | ||
print "From address: " . $NAS{'FROM'} . "\n"; | ||
print "Rsync binary location: " . $NAS{'RSYNC'} . "\n"; | ||
print "Rsync options to use: " . $NAS{'RSYNC_OPTIONS'} . "\n"; | ||
print "Rsync remote options to use: " . $NAS{'RSYNC_REMOTE_OPTIONS'} . | ||
"\n\n"; | ||
print "Simulating backup with dry rsync run:\n"; | ||
} | ||
return %NAS; | ||
} | ||
|
||
# Function: send_mail | ||
# Purpose: sends a mail VIA sendmail with given content | ||
# Requires: to - string To e-mail address | ||
# from - string From e-mail address | ||
# subject - string subject | ||
# body - string e-mail body | ||
sub send_mail { | ||
my ($to, $from, $subject, $body) = @_; | ||
my $sendmail = "/usr/sbin/sendmail -t"; | ||
open(SENDMAIL, "|$sendmail") or return 0; | ||
print SENDMAIL "To: $to\n"; | ||
print SENDMAIL "From: $from\n"; | ||
print SENDMAIL "Subject: $subject\n"; | ||
print SENDMAIL "Content-type: text/plain\n\n"; | ||
print SENDMAIL "$body\n"; | ||
close(SENDMAIL); | ||
} | ||
|
||
# Function: backup | ||
# Purpose: rsyncs the given NAS local and remote source directories to | ||
# destination directories | ||
# Requires: NAS - a hash reference containing the configuration keys, must be | ||
# run through clean_options first | ||
# Returns: True on success, False on non-writable or non-directory destination | ||
# Returns: A NAS hash with processed successes and failures. | ||
sub backup { | ||
# grab the NAS reference, and create a nice hash pointer to the NAS data | ||
my($NAS_reference, $check) = @_; | ||
my $NAS = %{$NAS_reference}; | ||
|
||
my $rsync_command = ''; | ||
foreach(@{$NAS{'LOCAL'}}) { | ||
$rsync_command = $NAS{'RSYNC'}." ". | ||
$NAS{'RSYNC_OPTIONS'}." '".$_. | ||
"' '".$NAS{'DESTINATION'}."'"; | ||
if( $check ) { print $rsync_command . "\n"; } | ||
if( system($rsync_command) != 0 ) { | ||
push(@{$NAS{'failures'}},$_); | ||
} else { | ||
push(@{$NAS{'successes'}},$_); | ||
} | ||
} | ||
foreach(@{$NAS{'REMOTE'}}) { | ||
$rsync_command = $NAS{'RSYNC'}." ". | ||
$NAS{'RSYNC_OPTIONS'}." ". | ||
$NAS{'RSYNC_REMOTE_OPTIONS'}."'".$_. | ||
"' '".$NAS{'DESTINATION'}."'"; | ||
if( $check ) { print $rsync_command . "\n"; } | ||
if( system($rsync_command) != 0 ) { | ||
push(@{$NAS{'failures'}},$_); | ||
} else { | ||
push(@{$NAS{'successes'}},$_); | ||
} | ||
} | ||
return %NAS; | ||
} | ||
|
||
# Function: check_duplicates | ||
# Purpose: checks for duplicate source directories in a given array | ||
# Requires: array - a array reference containing the directories to check | ||
# Returns: True on success, exits with e-mail and error(1) if failed | ||
sub check_duplicates { | ||
my($directories_reference) = @_; | ||
my @check_directories = @{$directories_reference}; | ||
my %duplicate_counter; | ||
my $last_dir_name; | ||
|
||
foreach(@check_directories) { | ||
# grab the last directory in the source string, and lowercase it to compare | ||
$last_dir_name = (split(/\//, $_))[-1]; | ||
$last_dir_name =~ tr/[A-Z]/[a-z]/; | ||
$duplicate_counter{$last_dir_name}++; | ||
if( $duplicate_counter{$last_dir_name} > 1 ) { | ||
my $body = "Your source directories have the same LAST directory name. ". | ||
"This WILL LEAD TO DATA LOSS, as only the last duplicate directory ". | ||
"is copied to the destination (not the full path). This will ". | ||
"overwrite the first directory backed up on the remote server.\n\n". | ||
"ABORTING TO PRESERVE DATA.\n--------------------------\n"; | ||
$body .= join("\n",@check_directories); | ||
send_mail($NAS{'TO'},$NAS{'FROM'}, | ||
"Critical backup ABORTED - duplicate sources detected!",$body); | ||
exit(1); | ||
} | ||
} | ||
return 1; | ||
} | ||
|
||
# Function: usage | ||
# Purpose: prints usage information, and exits with no error | ||
# Requires: none | ||
sub usage { | ||
print " | ||
critical_backup <OPTIONS> | ||
This will sync critical backup data (from the NAS and/or a remote system) to a | ||
directory on NAS (preferably read-only by normal users). By default, this | ||
program runs sliently. Edit the script to add/change directories that are | ||
backed up. | ||
OPTIONS: | ||
--check Turns on checking mode, showing processed options before | ||
execution, and DISABLES actual rsync transfers. This is | ||
useful to test that everything is setup correctly before | ||
running the script. | ||
--log Logs rync status, and transfer details to the terminal | ||
window. If run in a cronjob, you should redirect this | ||
output to a logfile somewhere. Useful for debugging, and | ||
verifying copied files. | ||
--no-space-escape By default, remote directories are quoted and escaped. | ||
However, some systems only handle quoting or escaping, but | ||
not both. Enable this flag if your remote directories | ||
have spaces in them, and FAIL for no apparent reason. | ||
This is most easily determined by running this program | ||
with the --check option, and looking for (code 23) errors | ||
during the rsync transfers. If you see those, setting | ||
this option should fix those errors and allow transfers on | ||
remote servers. | ||
--help / -h This help message. | ||
"; | ||
exit(0); | ||
} |
Oops, something went wrong.