So I finally got around to fixing something that's been bugging me since I set up this blog back in November. Techpad runs off a cheap Linode box, and up until last week my "backup strategy" was basically me remembering to SSH in every so often and manually tar up the MySQL dump. Which is a terrible plan, obviously. I forgot for seventeen days over Christmas and didn't even notice until New Year's Eve when I was poking around trying to fix something else. Nothing broke, thankfully, but it scared me enough to actually sit down and do this properly.
So: cron. If you've got a Linux server (or a Mac, cron's on there too) and you're doing anything repetitive by hand, you should probably stop doing that.
What cron actually is
Cron's just a scheduler that's been part of Unix-y systems forever. You give it a time and a command, it runs the command at that time, every time, forever, until you tell it not to. No daemon to install, no extra package in most cases, it's already running on your box right now doing... probably nothing, if you've never touched it.
To see what's already scheduled for your user, run:
crontab -l
Probably empty. To edit it:
crontab -e
First time you run that it might ask which editor you want. I pick vim out of habit even though nano is honestly friendlier for something this short. Anyway, once you're in there, each line is one scheduled job, and the format is five time fields followed by the command:
* * * * * command-to-run
Those five stars are, in order: minute, hour, day of month, month, day of week. So if I want my backup script to run every night at 3:15 AM, that's:
15 3 * * * /home/ramith/scripts/backup.sh
The stars mean "every" for that field. Want it to only run on Sundays at 3:15? Change the last star to 0 (cron counts Sunday as 0, which trips people up constantly). Want it every 6 hours instead of once a day? 15 */6 * * * does that.
The actual backup script
I'm not going to pretend my script is fancy. It's about twelve lines and it does three things: dumps the database, tars up the uploads folder, and pushes both to a second server over rsync. Something like this:
#!/bin/bash
DATE=$(date +%F)
mysqldump -u backupuser -pXXXXXX techpad_db > /home/ramith/backups/db-$DATE.sql
tar -czf /home/ramith/backups/uploads-$DATE.tar.gz /var/www/techpad/uploads
rsync -avz /home/ramith/backups/ user@otherserver:/backups/techpad/
Nothing clever. Make sure it's executable (chmod +x backup.sh) before cron tries to run it, or it'll just silently fail and you'll be sitting there a week later wondering why there's nothing in the backups folder.
The stuff that actually trips people up
Here's the thing nobody tells you: the environment cron runs your script in is NOT the same as your normal login shell. No .bashrc, way smaller $PATH. I burned about forty minutes the first time because my script called mysqldump with no path, worked fine when I ran it manually, then failed silently under cron because cron's minimal environment couldn't find it. Fix is either use the full path to every binary in your script (/usr/bin/mysqldump) or set PATH explicitly at the top of the script. I do both now out of paranoia.
Second thing: cron mails you the output of every job by default, assuming mail's configured on your box, which on a bare Linode droplet it usually isn't. So either set up mail, or (what I actually do) redirect output to a log file so you can check it later:
15 3 * * * /home/ramith/scripts/backup.sh >> /home/ramith/backups/backup.log 2>&1
That 2>&1 bit catches errors too, not just normal output. Leave that off and you'll only ever see the successes, which is exactly backwards from what you want.
Third, and this one's more of a personal preference than a rule: I keep my crontab lines commented, because six months from now I will have zero memory of what 47 2 * * 3 was supposed to do. A # line above each entry explaining it in plain English costs nothing.
Anyway. It's been running four nights now, log file's clean, and last night's backup landed on the second server at 3:16 AM like it was supposed to. Small thing, but I feel dumb for not doing it two months ago when I first set this whole site up.