instant-thinking.de

just enough to get you started and leave you confused

Synced and scheduled blogging with Octopress

| Kommentare

One feature of Wordpress that I really liked, was the ability to easily schedule the publication of a post for a specified date and time. Another was the seperation of drafts and posts in the webinterface.

I wanted these features back, but without all the clutter the Wordpress webinterface introduced in the past versions. It admittedly got better in the last version I used1, but the damage was done.

Now, firmly settled into my Octopress driven txt-only environment, I could implement these features on my own. Tailored to my very own needs. Throwing in some sync-o-magic to keep my posts and drafts updated across all my devices.

So if you’re into these things, follow me to see how posts get published in this corner of the Internet.

Search what other people have done

The very first thing to do, when you attempt to do anything remotely technical, is to check if other people encountered2 your current problem before you.

This incron-method was one of the first posts i found3 but it could not provide me with the seperation of drafts and the scheduling I grew so accustomed to. And I just don’t want my site to rebuild everytime I change a typo in an old post. Also: instant-thinking.de lives on kinda cheap shared webspace with no Dropbox-sync-o-matic capabilities. I would need to solve this with a different approach.

Another very interesting post I found is this one by Mr. Jonathan Poritsky. He is also using Dropbox to sync his site across his iOS Devices4 and his Mac at home.

This nicely mirrors my own setup:

  • A Mac mini at home, almost always on
  • A MacBook Pro, almost always at home, not always on
  • iPhone and iPad at home and on the go

He then continues to build the site manually via a SSH-login on his Mac and deploys the finished site via git push to a private git-repo at Beanstalk. From there he uses FTP to update his Website.

This sounded a bit cumbersome to me and so I came up with my own solution.

My own remote blogging setup

In additon the the queueing of posts and the separation of drafts, I added syncing across my devices to the mix. So here is what I needed to achieve:

  • Keep my drafts and posts seperated
  • Keep my drafts and posts synced across all my devices
  • Have an easy way, to change the status of my latest masterpiece5 from draft to post
  • Have an easy way, to choose a specific date and time for the publication of posts
  • Have an easy way, to automate the build and deployment - no manual sshish stuff, thank you very much

So, to first reach for the low hanging fruits:

Seperate drafts and posts

Inside each Octopress source-folder is a _posts folder containing all the posts. I simply created another folder _drafts to hold my drafts. For good measure, I patched the Rakefile to include a new task to create a new draft. Here is the diff:

Modified Rakefile for drafts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
@@ -27,6 +27,7 @@ blog_index_dir  = 'source'    # directory for your blog's index page (if you put
 deploy_dir      = "_deploy"   # deploy directory (for Github pages deployment)
 stash_dir       = "_stash"    # directory to stash posts for speedy generation
 posts_dir       = "_posts"    # directory for blog files
+drafts_dir       = "_drafts"    # directory for draft files
 themes_dir      = ".themes"   # directory for blog files
 new_post_ext    = "markdown"  # default new post file extension when using the new_post task
 new_page_ext    = "markdown"  # default new page file extension when using the new_page task
@@ -94,6 +95,32 @@ task :preview do
   [jekyllPid, compassPid, rackupPid].each { |pid| Process.wait(pid) }
 end

+# usage rake new_draft[my-new-draft] or rake new_draft['my new draft'] or rake new_draft (defaults to "new-draft")
+desc "Begin a new draft in #{source_dir}/#{drafts_dir}"
+task :new_draft, :title do |t, args|
+  raise "### You haven't set anything up yet. First run `rake install` to set up an Octopress theme." unless File.directory?(source_dir)
+  mkdir_p "#{source_dir}/#{drafts_dir}"
+  args.with_defaults(:title => 'new-draft')
+  title = args.title
+  filename = "#{source_dir}/#{drafts_dir}/#{Time.now.strftime('%Y-%m-%d')}-#{title.to_url}.#{new_post_ext}"
+  if File.exist?(filename)
+    abort("rake aborted!") if ask("#{filename} already exists. Do you want to overwrite?", ['y', 'n']) == 'n'
+  end
+  puts "Creating new draft: #{filename}"
+  open(filename, 'w') do |post|
+    post.puts "---"
+    post.puts "layout: post"
+    post.puts "title: \"#{title.gsub(/&/,'&')}\""
+    post.puts "date: #{Time.now.strftime('%Y-%m-%d %H:%M')}"
+    post.puts "comments: true"
+    post.puts "published: false"
+    post.puts "tags: "
+    post.puts "- "
+    post.puts " "
+    post.puts "---"
+  end
+end
+

Basically, this is just a copy of the new_post task. Please notice the added published: false in the YAML-header. This, as documented in Octopress Blogging Basics, would prevent this post from getting published if it was found in the _postsfolder. This will come in handy later…

It is of course still possible to create a new draft or post any other way. Just make sure the filename follows Octopress/Jekyll convention of YYYY-MM-DD-post-title.markdown, include the YAML-header and you’re good to go.

Sync across all my devices

Which brings me to syncing. These days, this usually means Dropbox. Apples iCloud could be another option one day, but right now it does not seem like Apple is going to send some love to the filesystem part of it’s operating systems. And because virtually any iOS text editor comes with Dropbox support, the choice for Dropbox is a no brainer.

So I logged into my Mac mini at home to create a new folder in my Dropbox. I named it, oh the creativity, blog. Inside this folder I put _drafts, _posts, _queue and images. Viewed from an iOS Dropbox-Client, it looks a lot like this:

These folders are actually symlinks that got created on the Mac mini like so:

Creating symlinks on the Mac mini
1
2
3
4
ln -s ~/instant-thinking/source/_posts/ ~/Dropbox/blog/_posts
ln -s ~/instant-thinking/source/images/ ~/Dropbox/blog/images
ln -s ~/instant-thinking/source/_drafts/ ~/Dropbox/blog/_drafts
ln -s ~/instant-thinking/source/_queue/ ~/Dropbox/blog/_queue

When finished, the blog-folder looks like this on the Mac mini:

Blog on the Mac mini
1
2
3
4
5
6
 ~/Dropbox/blog >  ls -l
total 32
lrwxr-xr-x  1 dennis  staff  46 24 Mär 19:53 _drafts -> /Users/dennis/instant-thinking/source/_drafts/
lrwxr-xr-x  1 dennis  staff  45 23 Mär 21:18 _posts -> /Users/dennis/instant-thinking/source/_posts/
lrwxr-xr-x  1 dennis  staff  45 24 Mär 20:03 _queue -> /Users/dennis/instant-thinking/source/_queue/
lrwxr-xr-x  1 dennis  staff  45 23 Mär 21:20 images -> /Users/dennis/instant-thinking/source/images/

On any other machine, these become actual folders:

Blog on the MacBook Pro
1
2
3
4
5
6
 ~/Dropbox/blog >  ls -l
total 0
drwxr-xr-x    13 dennis  staff    442 26 Jul 20:18 _drafts
drwxr-xr-x  1396 dennis  staff  47464 26 Jul 20:18 _posts
drwxr-xr-x     3 dennis  staff    102 18 Jun 20:21 _queue
drwxr-xr-x  1041 dennis  staff  35394 24 Jul 21:08 images

The underscores are there to make sure that these folders won’t get deployed to the live site. So all these folders, with the exception of images, are kept on the local machines. This way, all the essential folders containing my content are in sync across anything which can talk to Dropbox.

From draft to queue to published post

So now we have a perfectly synced draft. How will this draft get to the live site? And how will this happen at a given date and time?

This will happen with the help of two YAML-properties in the frontmatter and the it_queue.rb script which lives on the Mac mini and can also be found in this GitHub repo.

The script gets run every five minutes by a LaunchAgent6. It starts by looking into the YAML-frontmatter of every file in the _drafts-folder. If it finds a post with published:true in the frontmatter it will move it from _drafts to _queue. During this move, the script reads the date part of the date:-property and renames the file accordingly.

Let me explain:

A draft, created by the mentioned new_draft task on the 1st of May 2012, would live in the _drafts-folder as the file 2012-05-01-rant-on-some-apple-thingie.markdown. In it’s YAML-frontmatter it would carry the following properties7:

Draft YAML-frontmatter
1
2
date: 2012-05-01 10:33
published: false

After some work on it, I decide to mark this draft as finished on May, 15th by changing the properties to:

Post YAML-frontmatter
1
2
date: 2012-06-12 09:00
published: true

Next time the script runs, it moves the file to the _queue-folder, renaming it to 2012-06-12-rant-on-some-apple-thingie.markdown in the process.

The date: property not only defines the filename of the post, but also it’s publishing date and time.

This is, because it_queue.rb also checks the YAML-frontmatters of the posts in the _queue-folder. Here, it scans for the date and time of the date: property, transforms the found string into a Ruby time object and checks, if this object is in the past or in the future of Time.now. If it is in the future, nothing happens. If it is in the past, the build-variable is set to 1 and the build and deployment of instant-thinking.de is triggered.

The file in question would trigger the build on the first run of the script after June 12th, 2012, 09:00. During this run, the file gets moved out of _queueand into _posts. From there, it will get rendered and put on the live site.

Wrapping up the build and deployment

Both parts, build and deployment, turned out to be a bit more tricky than I thought…

The problems I encountered are nonexistent, if I go manually through the steps needed to build and deploy the site. But everything goes south when the LaunchAgent tries to automatically do it’s duty.

It all comes down to the environment used when the neccessary actions are performed. Things that needed to get fixed:

  • The Ruby version. The Mac mini runs OS X 10.6.8 Snow Leopard which comes with Ruby 1.8.7. Octopress needs Ruby 1.9.2, which I installed on the mini via RVM
  • The encoding. Each and every Umlaut caused the build to fail catastrophically
  • The SSH authentification. I use a passwordless SSH-login for the rsync-deployment. But the LaunchAgent has no access to the used key

To solve the problem with the Ruby version, I used the wrapper _rvmruby which I found at urgetopunt.com.

In the LaunchAgent, I do not call it_queue.rb directly, but use it as the argument to ruby, which in turn gets called with the Ruby version 1.9.2 provided by RVM. The whole LaunchAgent plist looks like this:

de.instant-thinking.it_queue.plist.example This plist on Github
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>Label</key>
    <string>de.instant-thinking.it_queue</string>
    <key>ProgramArguments</key>
    <array>
        <string>/absolute/path/to/_rvmruby</string>
        <string>1.9.2</string>
        <string>/absolute/path/to/it_queue.rb</string>
    </array>
    <key>StartCalendarInterval</key>
    <dict>
        <key>Hour</key>
        <integer>0</integer>
        <key>Minute</key>
        <integer>0</integer>
    </dict>
    <key>StartInterval</key>
    <integer>300</integer>
</dict>
</plist>

Following this approach, I modified the original wrapper to include de:DE.UTF-8-encoding just like in my own interactive sessions. This took care of all the Umlaut-problems I encountered.

Finally, I found a hint from Linc Davis on discussions.apple.com in which he explains, how a script can be told to use an existing SSH auth socket on OS X 10.5+. This approach requires, that the given user is logged into the system each time the script runs and that the script is run either under the same account or as root but that is pretty much a given on my Mac mini.

I had to change the name of the SSH auth socket variable from SSHAUTHSOCK to SSH_AUTH_SOCK but other than this nuisance, it worked like a charm.

My final wrapper looks like this:

Patched _rvmruby This wrapper on Github
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#!/bin/sh
# usage: _rvmruby <ruby version> [ruby arguments]
# via: http://urgetopunt.com/rvm/osx/2011/01/28/rvm-os-x-launchd.html

# Tell launchd to use UTF-8. Like a boss.
export LANG="de:DE.UTF-8"
export LC_COLLATE="de_DE.UTF-8"
export LC_CTYPE="de_DE.UTF-8"
export LC_MESSAGES="de_DE.UTF-8"
export LC_MONETARY="de_DE.UTF-8"
export LC_NUMERIC="de_DE.UTF-8"
export LC_TIME="de_DE.UTF-8"
export LC_ALL="de_DE.UTF-8"

# Tell launchd to use the existing SSH auth socket. 
# Slightly modified from: https://discussions.apple.com/message/8686462?messageID=8686462#8686462?messageID=8686462
# The variable contains underscores, these were missing in the source
export SSH_AUTH_SOCK=$( find /tmp/launch-*/Listeners -user dennis -type s | head -1 )

if [[ -s /Users/dennis/.rvm/scripts/rvm ]]; then
  . /Users/dennis/.rvm/scripts/rvm
fi
ruby_version=$1; shift
rvm $ruby_version
exec ruby "$@"

And that’s pretty much it.

The scheduling is not exact to the minute because the script only runs every five minutes and even then, the build of instant-thinking.de takes almost 15 minutes on the Mac mini.

But the fact, that I can decide when my drafts become posts and when they’ll get published is really quite convenient. This is nothing I would want to write a liveblog with, but for my usual posting frequency, it is really convenient.

Even more, if you consider that the whole Wordpress date picker thingie with buttons and bells and whistles is replaced by two lines in a txt-file. A little Ruby-script and some tinkering with YAML-properties is all it takes to use Octopress in a much more pleasing way. A blogging framework for hackers, indeed.

If this setup suits your needs, you can find everything you need in the Github Repo. Have fun, and if you made it down here, thanks for reading.

  1. Version 3.2.x

  2. And, if things are going fine, solved it, too…

  3. Back when I still pondered the use of pure Jekyll

  4. Or whatever he might fancy in the future, just make sure it uses plain text and Dropbox

  5. Ahem

  6. Actually, it gets run by a wrapper script which gets run by the LaunchAgent but we’ll get to this shortly…

  7. And some more of course…

Comments