Website rsync Backups the Time Machine Way

Continuing my recent rash of stupid coding, after Spellcheck the Shell Way, I decided for Website rsync Backups the Time Machine Way.

For a few years now, I’ve been using a bash script I bodged together that does incremental-ish backups of my websites using the rather formidable rsync. This week I’ve been working for maschinentempel.de, helping get frohstoff.de‘s WooCommerce shop from Trabant to Hoonage. Which required repeated backing up of the entire site and database, and made me realise the shoddiness of my original backup script.

I thought, “Wouldn’t it be awesome, instead of having to make those stupid ‘backup.blah’ folders, to let the script create a time-stamped folder like Time Machine for each backup, and use the most recent backup for the rsync hard links link destination?” Fukken wouldn’t it, eh?

Creating time-stamped folders was easy. Using the most recent backup folder — which has the most recent date, and in standard list view on my Mac, the last folder in a list — was a little trickier. Especially because once a new folder was created to backup into, that previously most recent was now second to last. tail and head feels hilariously bodgy, but works? Of course it does.

Bare bones explaining: The script needs to be in a folder with another folder called ‘backups’, and a text file called ‘excludes.txt’.  Needs to be given chmod +x to make it executable, and generally can be re-bodged to work on any server you can ssh into. Much faster, more reliable, increased laziness, time-stamped server backups.

#!/bin/sh
# ---------------------------------------------------------------
# A script to manually back up your entire website
# Backup will include everything from the user directory up
# excludes.txt lists files and folders not backed up
# Subsequent backups only download changes, but each folder is a complete backup
# ---------------------------------------------------------------
# get the folder we're in
this_dir="`dirname \"$0\"`"
# set the folder in that to backup into
backup_dir="$this_dir/backups"
# cd to that folder
echo "******************"
echo "cd-ing to $backup_dir"
echo "******************"
cd "$backup_dir" || exit 1
# make a new folder with timestamp
time_stamp=$(date +%Y-%m-%d-%H%M%S)
mkdir "$backup_dir/${backuppath}supernaut-${time_stamp}"
echo "created backup folder: supernaut-${time_stamp}"
echo "******************"
# set link destination for hard links to previous backup
# this gets the last two folders (including the one just made)
# and then the first of those, which is the most recent backup
link_dest=`ls | tail -2 | head -n 1`
echo "hardlink destination: $link_dest"
echo "******************"
# set rsync backup destination to the folder we just made
backup_dest=`ls | tail -1`
echo "backup destination: $backup_dest"
echo "******************"
# run rsync to do the backup via ssh with passwordless login
rsync -avvzc --hard-links --delete --delete-excluded --progress --exclude-from="$this_dir/excludes.txt" --link-dest="$backup_dir/$link_dest" -e ssh username@supernaut.info:~/ "$backup_dir/$backup_dest"
echo "******************"
echo "Backup complete"
echo "******************"
#------------------------------------------------
# info on the backup commands:
# -a --archive archive mode; same as -rlptgoD (no -H)
# -r --recursive recurse into directories
# -l --links copy symlinks as symlinks
# -p --perms preserve permissions
# -t --times preserve times
# -g --group preserve group
# -o --owner preserve owner (super-user only)
# -D same as --devices --specials
# --devices preserve device files (super-user only)
# --specials preserve special files
# -v --verbose increase verbosity - can increment for more detail i.e. -vv -vvv
# -z --compress compress file data during the transfer
# -c --checksum skip based on checksum, not mod-time & size – SLOWER
# -H --hard-links preserve hard links
# --delete delete extraneous files from dest dirs
# --delete-excluded also delete excluded files from dest dirs
# --progress show progress during transfer
# --exclude-from=FILE read exclude patterns from FILE – one file or folder per line
# --link-dest=DIR hardlink to files in DIR when unchanged – set as previous backup
# -e --rsh=COMMAND specify the remote shell to use – SSH
# -n --dry-run show what would have been transferred

A Bit of Character Counting Stupidity

When I’m using WordPress’ Status Post Format, I like to keep it to 140 characters, so it’s like a Tweet. But how many characters have I typed? Cos The Visual Editor only shows Word Count. So I took a look around and saw various ways of doing it, quite a few using regex to strip ‘unwanted characters’—but a space counts as a character! So I wrote my own, based loosely on what I’d been using for counting characters in the Excerpt, and from a few different plugins and bits of code. Turned out to be surprisingly easy and uncomplicated (I say that now, of course).

So, first I need a function to call a couple of files if I happen to be editing a Post or Page:

function supernaut_character_count( $charcount ) {
  if ( 'post.php' == $charcount || 'post-new.php' == $charcount ) {
    wp_enqueue_style( 'character-count-css', get_stylesheet_directory_uri() . '/css/char-count.css', array() );
    wp_enqueue_script( 'character-count-js', get_stylesheet_directory_uri() . '/js/char-count.js', array( 'jquery' ), '20160519', true );
  }
  else return;
}
add_action( 'admin_enqueue_scripts', 'supernaut_character_count' );

Then I slap together some jQuery:

jQuery( document ).ready( function( $ ) {
  if( $( '.post-php' ).length || $( '.post-new-php' ).length ) {
    $( '#post-formats-select input' ).click( function() {
      if ( $('#post-format-status').is(':checked') ) {
        $( '#post-status-info tr:first-child' ).after( 'Character count: ' );
        $( '#content_ifr' ).ready( function () {
          setInterval( function() {
            var tinymcebody = $( '#content_ifr' ).contents().find( 'body' );
            $( '#postdivrich .character-count' ).html( tinymcebody.text().length );
          }, 500 )
        });
      } else {
        $( '#post-status-info tr:first-child' ).nextAll().remove();
      }
    });
  }
});

Then a miniscule bit of CSS:

.post-php #wp-character-count {
  font-size: 12px;
  line-height: 1;
  display: block;
  padding: 0 10px 4px;
}

It’s a little bodgy, and if I had more than the 45 minutes to a) work out how the Visual Editor can be fiddled with, and b) write something that worked and looked ok—the bare minimum really—I’d do it slightly nice and maybe consider for a minute throwing it into a plugin (where it officially belongs). But I won’t. It does what I want: live updating of how many characters I’ve written in the Visual Editor, and shows it in a line underneath the Word Count (also aided me in delaying writing a residency application on the day it’s due).

supernaut Character Count
supernaut Character Count

Reading … What?

Aside

I am actually reading – to explain lack of book blogging. Merely finishing off a few in my pile of the non-fiction type I realised I’d left part-way through when I unpacked all my shelves a couple of months ago. New shit pile is currently getting dealt to, plus imminent arrival of a dozen or so tasty numbers.

Slight Improvements to Accessibility & Structured Data

Coding while sick!

Recent supernaut museum photography blogging got me thinking about image metadata (both Exif for camera-applied metadata, and IPTC for image-specific person-added metadata), and how I could set up a workflow to better implement this – particularly IPTC which is something I’d need to add and can partially automate, rather than Exif, which the camera adds – and how to persuade WordPress (or straight PHP / some method of database demonology) to scrape that and output it into Schema structured data.

I started doing heavy Schema work on tiptree.org as a way of making the huge amount of data comprehensible to search engines, and by extension, to humans, and have grown to like it a lot (also microformats, which WordPress uses) for making sense of what is otherwise a string of decontextualised words and media. I’d already schema-fied Dasniya’s blog, so mostly it was copy-pasting, then combining into supernaut’s structure – where the design had taken far too many liberties with accessibility and structured data.

So, now all posts spit out at least minimally useful structured data, and posts with images, galleries, or video spit out additional Schema structured data for those objects. Once I start using IPTC for my images, I’ll look at a way of including that in the Schema as well. (Though combining all that into useful objects from experience with Tiptree can be hilariously obtuse.)

On the accessibility side, I’d realised sometime in the past I’d been seriously shitty in supernaut’s design, basically a fucking horrible website to get around for anyone using keyboard navigation, screen-readers, or other methods non-trackpad + eyeballs. Accessibility is something I’ve been increasingly enjoying coding and designing for, along with structured data, particularly for websites that are highly designed and plain fucking arty – I find it gratifying to make hugely complex sites that remain structurally coherent and accessible no matter what device or method a person uses to access it – because there’s often an inverse relationship between design ‘woo!’ and usability ‘yay!’

So, keyboard navigation now is useable and hopefully much clearer, visual styling of user interaction also. Not as good as it could be, but getting there, and vaguely aiming for all of supernaut to be at least somewhat accessible – including and especially my museum visits.