<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>EdSalisbury.net : Your Guide to User-Friendly Entertainment &#187; Unix</title>
	<atom:link href="http://www.edsalisbury.net/category/unix/feed/" rel="self" type="application/rss+xml" />
	<link>http://www.edsalisbury.net</link>
	<description>Your Guide to User-Friendly Entertainment</description>
	<lastBuildDate>Mon, 14 Jun 2010 00:38:57 +0000</lastBuildDate>
	<generator>http://wordpress.org/?v=2.9.2</generator>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
			<item>
		<title>Setting up Differential Backups for Websites</title>
		<link>http://www.edsalisbury.net/linux/setting-up-differential-backups-for-websites/</link>
		<comments>http://www.edsalisbury.net/linux/setting-up-differential-backups-for-websites/#comments</comments>
		<pubDate>Mon, 14 Sep 2009 05:46:35 +0000</pubDate>
		<dc:creator>Ed</dc:creator>
				<category><![CDATA[Linux]]></category>
		<category><![CDATA[Unix]]></category>
		<category><![CDATA[Web]]></category>
		<category><![CDATA[backup]]></category>
		<category><![CDATA[disaster recovery]]></category>
		<category><![CDATA[website]]></category>

		<guid isPermaLink="false">http://www.edsalisbury.net/?p=452</guid>
		<description><![CDATA[
Backups.  No one likes doing them, but they&#8217;re too important not to do.  Typically when I&#8217;m doing stuff for my websites, I will occasionally do a tar of a directory and save it off, or even set up a script that will do this nightly.  Then I have issues with disks filling [...]]]></description>
			<content:encoded><![CDATA[<p><a href="http://www.flickr.com/photos/roadhunter/68017710/"><img src="http://www.edsalisbury.net/wp-content/uploads/2009/09/backup-150x144.jpg" alt="Backup Backup Backup by Topato" title="Backup Backup Backup by Topato" width="150" height="144" class="alignright size-thumbnail wp-image-453" /></a><br />
Backups.  No one likes doing them, but they&#8217;re too important not to do.  Typically when I&#8217;m doing stuff for my websites, I will occasionally do a tar of a directory and save it off, or even set up a script that will do this nightly.  Then I have issues with disks filling up, and not wanting to delete my backups for fear of losing something (like the 20 copies aren&#8217;t enough!)<br />
<span id="more-452"></span><br />
Recently, I decided to put an end to my backup worries, and write a differential backup system.  What is a differential backup?  Go go gadget <a href="http://en.wikipedia.org/wiki/Incremental_backup">Wikipedia</a>!  Basically, a differential backup means that I won&#8217;t be filling up my drives any time soon, because I&#8217;m not doing a FULL backup each time &#8212; it&#8217;s only backing up the changes that have been made since the last full backup, which runs weekly and monthly.  I&#8217;ve been running and tuning the script for a while, and wanted to make sure that it&#8217;s working properly.  If you decide to use this script, you <strong>MUST</strong> test the restores with it, as I can&#8217;t be responsible if you lose data and the script somehow fails you.  You have been warned!  Without further ado, the script:</p>
<pre class="brush:perl; gutter: false; wrap-lines: true; ruler: false">
#!/usr/bin/perl
# Website Differential Backup
# Keeps several websites and their databases backed up
# by Ed Salisbury (ed@edsalisbury.net)
# http://www.edsalisbury.net
# (c)2009 Ed Salisbury, Some Rights Reserved
#
# Config File Format
# DIR /path/to/dir
# DB dbname
#
# Limitations:
# * Cannot do differential backups of databases
#
#
# License:
# Except where otherwise noted, this work is licensed under Creative Commons
#   Attribution ShareAlike 3.0.
#
# You are free:
#   * to Share - to copy, distribute and transmit the work
#   * to Remix - to adapt the work
#
# Under the following conditions:
#   * Attribution. You must attribute the work in the manner specified by the
#     author or licensor (but not in any way that suggests that they endorse
#     you or your use of the work).
#   * Share Alike. If you alter, transform, or build upon this work, you may
#     distribute the resulting work only under the same, similar or a
#     compatible license.
#   * For any reuse or distribution, you must make clear to others the license
#     terms of this work. The best way to do this is with a link to the
#     license's web page (http://creativecommons.org/licenses/by-sa/3.0/)
#   * Any of the above conditions can be waived if you get permission from the
#   * Nothing in this license impairs or restricts the author's moral rights.

use strict;
use warnings;

# Place to store backups
my $BACKUP_DIR = &quot;/backup&quot;;
# Configuration file to read what to backup
my $BACKUP_CONF = &quot;/usr/local/etc/backup.conf&quot;;
# Database user/pass
my $DB_USER = 'root';
my $DB_PASS = '**DBPASSWD**';

# Turn off output buffering
$|++;

# Tools
my $TAR = '/bin/tar';
my $GZIP = '/bin/gzip';
my $LS = '/bin/ls';
my $HEAD = '/usr/bin/head';
my $MKDIR = '/bin/mkdir';
my $RM = '/bin/rm';
my $MYSQLDUMP = '/usr/local/mysql/bin/mysqldump';

my @months = qw( Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec );
my @days = qw( Sun Mon Tue Wed Thu Fri Sat );
my @dirs;
my @dbs;

# Get current date
my ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst) = localtime time;
my $datestamp = sprintf(&quot;%04d%02d%02d&quot;, $year+1900, $mon+1, $mday);
my $day = $days[$wday];

# Read config file
unless (open(CFG, $BACKUP_CONF))
{
    print &quot;Error: Cannot open $BACKUP_CONF\n&quot;;
    exit(1);
}
while (&lt;CFG&gt;)
{
    chomp;
    if (/^DIR\s+(.*)$/i)
    {
        push (@dirs, $1);
    }
    elsif (/^DB\s+(.*)$/i)
    {
        push (@dbs, $1);
    }
}
close (CFG);

# Do Directory Backups
foreach my $dir (@dirs)
{
    print &quot;Backing up $dir... &quot;;

    # Add backslashes for spaces
    $dir =~ s/ /\\ /g;

    my $backup_src = $dir;

    # Convert slashes to underscores
    $dir =~ s/\//_/g;
    # Remove leading slash
    $dir = substr($dir,1);

    unless (-d &quot;$BACKUP_DIR/$dir&quot;)
    {
        system(&quot;$MKDIR -p $BACKUP_DIR/$dir&quot;);
    }

    # Get datestamp of last full backup
    my $last_full = `$LS -t $BACKUP_DIR/$dir/${dir}_full* 2&gt;&amp;1 | $HEAD -1`;
    chomp($last_full);
    if ($last_full =~ /No such file or directory/)
    {
        $last_full = '';
    }
    my ($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size,$atime,$mtime,$ctime,
        $blksize,$blocks) = stat($last_full);

    if ($mday == 1)
    {
        # Monthly Full Backup
        system(&quot;$TAR -cf $BACKUP_DIR/$dir/${dir}_full_monthly_$datestamp.tar $backup_src &gt; /dev/null 2&gt;&amp;1&quot;);
        system(&quot;$GZIP -f $BACKUP_DIR/$dir/${dir}_full_monthly_$datestamp.tar&quot;);
        system(&quot;$RM $BACKUP_DIR/$dir/${dir}_full_weekly_*&quot;);
    }
    elsif ($wday == 0 || !$last_full)
    {
        # Weekly Full Backup
        system(&quot;$TAR -cf $BACKUP_DIR/$dir/${dir}_full_weekly_$datestamp.tar $backup_src  &gt; /dev/null 2&gt;&amp;1&quot;);
        system(&quot;$GZIP -f $BACKUP_DIR/$dir/${dir}_full_weekly_$datestamp.tar&quot;);
    }

    # Daily Differential Backup
    if ($mtime)
    {
        my $newer = sprintf(&quot;%02d-%s&quot;, (localtime($mtime))[3], $months[(localtime($mtime))[4]]);
        system(&quot;$TAR --newer $newer -cf $BACKUP_DIR/$dir/${dir}_diff_daily_$day.tar $backup_src &gt; /dev/null 2&gt;&amp;1&quot;);
    }
    else
    {
        system(&quot;$TAR -cf $BACKUP_DIR/$dir/${dir}_diff_daily_$day.tar $backup_src &gt; /dev/null 2&gt;&amp;1&quot;);
    }
    system(&quot;$GZIP -f $BACKUP_DIR/$dir/${dir}_diff_daily_$day.tar&quot;);

    print &quot;done.\n&quot;;
}

# Do Database Backups
foreach my $db (@dbs)
{
    my $db_name = $db;
    $db = &quot;db_$db&quot;;

    unless (-d &quot;$BACKUP_DIR/$db&quot;)
    {
        system(&quot;$MKDIR -p $BACKUP_DIR/$db&quot;);
    }

    print &quot;Backing up $db_name database... &quot;;

    if ($mday == 1)
    {
        # Monthly Backup
        system(&quot;$MYSQLDUMP --user=$DB_USER --password='$DB_PASS' $db_name &gt; $BACKUP_DIR/$db/${db}_monthly_$datestamp.sql&quot;);
        system(&quot;$GZIP -f $BACKUP_DIR/$db/${db}_monthly_$datestamp.sql&quot;);
        system(&quot;$RM $BACKUP_DIR/$db/${db}_weekly_*&quot;);
    }
    elsif ($wday == 0)
    {
        # Weekly Backup
        system(&quot;$MYSQLDUMP --user=$DB_USER --password='$DB_PASS' $db_name &gt; $BACKUP_DIR/$db/${db}_weekly_$datestamp.sql&quot;);
        system(&quot;$GZIP -f $BACKUP_DIR/$db/${db}_weekly_$datestamp.sql&quot;);
    }

    # Daily Backup
    system(&quot;$MYSQLDUMP --user=$DB_USER --password='$DB_PASS' $db_name &gt; $BACKUP_DIR/$db/${db}_daily_$day.sql&quot;);
    system(&quot;$GZIP -f $BACKUP_DIR/$db/${db}_daily_$day.sql&quot;);

    print &quot;done.\n&quot;;
}
</pre>
<p>Call the script something like /usr/local/bin/backup, and then create a config file /usr/local/etc/backup.conf, that has the following format:<br />
DIR /www/domain1.com<br />
DIR /www/domain2.com<br />
DB domain1_wp_db<br />
DB domain2_wp_db</p>
<p>(Substituting locations/db names appropriate for your environment of course).   If you use a different config file than /usr/local/etc/backup.conf, be sure to change that line in the script.  Also, you&#8217;ll need to change the mysql root password in the script to be whatever it is for your env (or make it non-root, whichever you want, it just needs to be able to do a mysqldump on the databases)</p>
<p>Next, set up your crontab to run nightly:</p>
<pre>sudo crontab -e</pre>
<p>Add the following line:</p>
<pre>0 1 * * * /usr/local/bin/backup &gt; /dev/null 2&gt;&#038;1</pre>
<p>That should be it!  Again, make sure to do some test restores to verify that it works ok!  Here&#8217;s what a typical backup directory looks like after a couple of months running the script:</p>
<pre>[ed@halo1:/backup/www_edsalisbury.net]$ ls -l
total 144240
-rw-r--r--  1 root root  5423759 Sep 11 01:00 www_edsalisbury.net_diff_daily_Fri.tar.gz
-rw-r--r--  1 root root  1839115 Sep  7 01:00 www_edsalisbury.net_diff_daily_Mon.tar.gz
-rw-r--r--  1 root root  5787136 Sep 12 01:00 www_edsalisbury.net_diff_daily_Sat.tar.gz
-rw-r--r--  1 root root  7193167 Sep 13 01:00 www_edsalisbury.net_diff_daily_Sun.tar.gz
-rw-r--r--  1 root root  3782727 Sep 10 01:00 www_edsalisbury.net_diff_daily_Thu.tar.gz
-rw-r--r--  1 root root  2455973 Sep  8 01:00 www_edsalisbury.net_diff_daily_Tue.tar.gz
-rw-r--r--  1 root root  3268897 Sep  9 01:00 www_edsalisbury.net_diff_daily_Wed.tar.gz
-rw-r--r--  1 root root 23705401 Aug  1 01:00 www_edsalisbury.net_full_monthly_20090801.tar.gz
-rw-r--r--  1 root root 33975458 Sep  1 01:00 www_edsalisbury.net_full_monthly_20090901.tar.gz
-rw-r--r--  1 root root 31453170 Sep  6 01:00 www_edsalisbury.net_full_weekly_20090906.tar.gz
-rw-r--r--  1 root root 28556424 Sep 13 01:00 www_edsalisbury.net_full_weekly_20090913.tar.gz
</pre>
<p>Not too bad, size-wise.  For all of my sites, the backup directory is about 4 GB, and will stay around that size for quite a while.  The differentials get deleted and so do the weekly full backups after the monthly full completes.  This makes it so that the directory only really grows when there&#8217;s a new full monthly.   If you find this script useful or have issues, be sure to post a comment!</p>
]]></content:encoded>
			<wfw:commentRss>http://www.edsalisbury.net/linux/setting-up-differential-backups-for-websites/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Installing Perl Modules on Ubuntu Linux</title>
		<link>http://www.edsalisbury.net/linux/installing-perl-modules-on-ubuntu/</link>
		<comments>http://www.edsalisbury.net/linux/installing-perl-modules-on-ubuntu/#comments</comments>
		<pubDate>Wed, 08 Jul 2009 14:00:02 +0000</pubDate>
		<dc:creator>Ed</dc:creator>
				<category><![CDATA[Linux]]></category>
		<category><![CDATA[Unix]]></category>
		<category><![CDATA[cpan]]></category>
		<category><![CDATA[perl]]></category>

		<guid isPermaLink="false">http://www.edsfamily.com/ed/archives/10</guid>
		<description><![CDATA[
I&#8217;ve been a perl programmer/scripter/hacker for many years now, and I will frequently use CPAN modules to be able to do the out-of-the ordinary.  This small guide is intended to help people easily install these modules.
First, let&#8217;s get the CPAN module up to date:
$ sudo perl -MCPAN -e shell
(take defaults to set everything up)
cpan&#62; [...]]]></description>
			<content:encoded><![CDATA[<p><img src="http://www.edsalisbury.net/wp-content/uploads/2007/12/perl_image-138x150.jpg" alt="perl" title="perl" width="138" height="150" class="alignright size-thumbnail wp-image-319" /><br />
I&#8217;ve been a perl programmer/scripter/hacker for many years now, and I will frequently use <a href="http://www.cpan.org/">CPAN</a> modules to be able to do the out-of-the ordinary.  This small guide is intended to help people easily install these modules.</p>
<p>First, let&#8217;s get the CPAN module up to date:</p>
<p><code>$ sudo perl -MCPAN -e shell</code><br />
(take defaults to set everything up)<br />
<code>cpan&gt; exit</code><br />
<code>$ sudo perl -MCPAN -e shell<br />
cpan&gt; install Bundle::CPAN</code><br />
(answer yes to the question about saving data)<br />
<code>cpan&gt; reload cpan</code></p>
<p>Then, to install a module, it&#8217;s simply:<br />
<code>$ sudo perl -MCPAN -e shell<br />
cpan&gt; install &lt;name of module&gt;</code><br />
<code>cpan&gt; exit</code></p>
]]></content:encoded>
			<wfw:commentRss>http://www.edsalisbury.net/linux/installing-perl-modules-on-ubuntu/feed/</wfw:commentRss>
		<slash:comments>3</slash:comments>
		</item>
		<item>
		<title>Asynchronous Mirroring in Unix</title>
		<link>http://www.edsalisbury.net/linux/asynchronous-mirroring-in-unix/</link>
		<comments>http://www.edsalisbury.net/linux/asynchronous-mirroring-in-unix/#comments</comments>
		<pubDate>Mon, 06 Jul 2009 14:00:11 +0000</pubDate>
		<dc:creator>Ed</dc:creator>
				<category><![CDATA[Linux]]></category>
		<category><![CDATA[Unix]]></category>
		<category><![CDATA[data]]></category>
		<category><![CDATA[mirroring]]></category>

		<guid isPermaLink="false">http://www.edsalisbury.net/?p=306</guid>
		<description><![CDATA[
One of the issues I&#8217;ve found with having lots of data is the fact that I&#8217;m worried that a hard drive will fail, and I&#8217;ll lose something important.  Since I did have that happen earlier this year, I am now determined to not let it happen again.  I&#8217;ve been focusing a lot on [...]]]></description>
			<content:encoded><![CDATA[<p><img src="http://www.edsalisbury.net/wp-content/uploads/2009/07/unix-150x150.jpg" alt="Un*x" title="Un*x" width="150" height="150" class="alignleft size-thumbnail wp-image-308" /><br />
One of the issues I&#8217;ve found with having lots of data is the fact that I&#8217;m worried that a hard drive will fail, and I&#8217;ll lose something important.  Since I did have that happen earlier this year, I am now determined to not let it happen again.  I&#8217;ve been focusing a lot on resiliency &#8211; making it so that there are no single points of failure.  Since I have been collecting a lot of hard drives, I decided to put them to good use, and set up some data replication onto multiple drives.</p>
<p>You might ask, why not just set up a RAID5?  Or, alternatively, why not buy a Drobo and be done with it?  I hear the first one a lot, especially since I deal with RAID day in and day out for my day job.  The main issue I don&#8217;t do either of these things is that I&#8217;m cheap!  I want to be able to use all of the hardware I have without tossing out old stuff.  On my server, I have the following drives:</p>
<p><span id="more-306"></span></p>
<ul>
<li>disk1: 120 GB ATA</li>
<li>disk2: 200 GB ATA</li>
<li>disk3: 300 GB USB</li>
<li>disk4: 400 GB Firewire</li>
<li>disk5: 600 GB SATA</li>
<li>disk6: 1000 GB USB</li>
<li>disk7: 1500 GB USB</li>
</ul>
<p>Try to set up a RAID with all of that!  The problem with this is the disparate drive sizes (RAID requires disks to be the same size, and will use the lowest common denominator if not.)  I&#8217;ve been &#8216;collecting&#8217; hard drives for quite a while, and I like the fact that I can just go to the local electronics store and pick up another drive when I run out of space.  This configuration does have a downside though, and that is, how do you decide where to put everything?  I decided to sit down and figure out how much space I actually need for each of my data types, and came up with this list (taking approximate growth rates into consideration):</p>
<ul>
<li>Photos: 100 GB</li>
<li>Music: 300 GB</li>
<li>Movies: 1 TB</li>
<li>TV: 400 GB</li>
<li>Software: 150 GB</li>
</ul>
<p>I then tried to map things out onto the drives I have:</p>
<ul>
<li>disk1: Photos</li>
<li>disk2: Software</li>
<li>disk3: Music</li>
<li>disk4: TV</li>
<li>disk6: Movies</li>
</ul>
<p>This leaves two disks, disk5 (600 GB) and disk7 (1500 GB) left to be able to mirror to.  Since I can&#8217;t use standard mirroring software, I&#8217;m not able to have a real-time mirror.  This is OK for my purposes, as I don&#8217;t intend on having them be hot-swappable, etc. &#8212; I just want another copy of my data out there, without having to worry about making backups all the time.</p>
<p>I set up my server to only export the data disks (I don&#8217;t want to be able to write to the backups from my desktop, as the changes will be overwritten by the backups)  I also set up my filesystem in a convenient manner &#8212; everything is a directory off of /data:</p>
<ul>
<li>/data/Photos</li>
<li>/data/Video</li>
<li>/data/Software</li>
<li>/data/Audio</li>
</ul>
<p>This makes it very easy to get to any of my data  (try that in Windows!)</p>
<p>One inherent advantage of having an asynchronous mirror is the fact that it can serve as a temporary backup if you accidentally delete a file/directory, whereas with a normal mirror, you&#8217;d still have to back up to tape or some other drive to be able to get this data back.</p>
<p>To do the actual mirroring, I opted to create a simple rsync script that will copy the data nightly from cron.  Nothing overly complex, but something that makes it easy to add new filesystems via a config file:</p>
<pre class="brush:perl; gutter: false; wrap-lines: true; ruler: false">
#!/usr/bin/perl
# Mirror
# Asynchronously mirror filesystems on a local machine
# by Ed Salisbury (ed@edsalisbury.net)
# http://www.edsalisbury.net
# (c)2009 Ed Salisbury, Some Rights Reserved
#
# External Utilities Required:
# * rsync
#
# License:
# Except where otherwise noted, this work is licensed under Creative Commons
#   Attribution ShareAlike 3.0.
#
# You are free:
#   * to Share — to copy, distribute and transmit the work
#   * to Remix — to adapt the work
#
# Under the following conditions:
#   * Attribution. You must attribute the work in the manner specified by the
#     author or licensor (but not in any way that suggests that they endorse
#     you or your use of the work).
#   * Share Alike. If you alter, transform, or build upon this work, you may
#     distribute the resulting work only under the same, similar or a
#     compatible license.
#   * For any reuse or distribution, you must make clear to others the license
#     terms of this work. The best way to do this is with a link to the
#     license's web page (http://creativecommons.org/licenses/by-sa/3.0/)
#   * Any of the above conditions can be waived if you get permission from the
#     copyright holder.
#   * Nothing in this license impairs or restricts the author's moral rights.

use warnings;
use strict;

# Configuration file for mirrors
# Example Config:
# /data/Photos/       /backup/Backup/Photos/
# /data/Software/     /backup/Backup/Software/
# /data/Audio/        /backup/Backup/Audio/
# /data/Video/        /backup/Backup/Video/
# /data/Misc/         /backup/Backup/Misc/
my $CONFIG = "/usr/local/etc/mirror.cfg";

open(CFG, $CONFIG);
my @lines = &lt;CFG&gt;;
close(CFG);

foreach my $line (@lines)
{
    my ($src, $dest) = split(/\s+/, $line);
    system("/usr/bin/rsync -av --delete $src $dest");
}
</pre>
<p>I put this script into /usr/local/bin, and then created a config file /usr/local/etc/mirror.conf similar to this:</p>
<pre>
/data/Photos/       /backup/Backup/Photos/
/data/Software/     /backup/Backup/Software/
/data/Audio/        /backup/Backup/Audio/
/data/Video/        /backup/Backup/Video/
/data/Misc/         /backup/Backup/Misc/
</pre>
<p>I then added a cronjob to have it run nightly:</p>
<pre>0 0 * * * /bin/backup > /dev/null 2>&#038;1</pre>
<p><strong><br />
A word of caution: Make *sure* you have the directories right!  rsync is set up to *DELETE* files in the destination if the file isn&#8217;t in the source directory, so if you have it rsync to two different directories, it will delete files as needed in the destination to sync them up.  You have been warned!  I take no responsibility for you deleting your data!</strong></p>
<p>Please let me know if you found this guide/script useful.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.edsalisbury.net/linux/asynchronous-mirroring-in-unix/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
	</channel>
</rss>
