Scheduling Posts and Microblogs With Jekyll

- 7 min -
Joe Buhlig

When I switched this blog to Jekyll, one of the features I lost was the ability to schedule posts for the future. Yes, Jekyll can handle future dates on posts, but you still need to run a command to build the site and then deploy it to your web server.

And I have been operating without that ability for over four years now. But this past December I decided to be done with manually posting articles and setting aside time to do the deployment by hand. So I set about building the following structure to allow me to publish new articles and microblogs to the site.

build.sh

jekyll-repo/build.sh
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#!/bin/bash

cd /path/to/repo/

. /home/username/.bashrc

git fetch origin master --quiet
mycommit=$(git rev-parse HEAD)
origincommit=$(git rev-parse origin/master)
if [ "$mycommit" = "$origincommit" ]
then
  bundle exec rake publish
else
  git reset --hard origin/master
  git pull
  bundle install > /dev/null
  bundle exec rake build
  bundle exec whenever --update-cron
fi

This all starts with a build script. It sits at the top-level of the repo: my-repo/build.sh. All this does is check for new git commits. If no new commits exist, it runs a rake task to see if there are any new posts or microblogs available. More on that later.

If it does find a new commit, there are a few steps it takes. First, it resets the local repository, pulls the new commit, and then runs a bundle install. This allows me to update the Gemfile and add any dependencies I may need without logging into the webserver and updating it.

Then it runs the build task. Again, more on that below. But after the build task, it runs the whenever command to update my cron tasks, which begs the question: what cron tasks?

Cron Job

I added gem whenever to my Gemfile and then put this in config/schedule.rb:

jekyll-repo/config/schedule.rb
1
2
3
4
5
set :output, "/path/to/log/my.log"

every 5.minutes do
 command "sh /path/to/repo/build.sh"
end

By using the whenever gem, I can update the server cron job(s) by simply committing the changes to the repo. The build script will pull the changes and run the command to update the cron job.

At this point, I hope you can see the beauty of this. The build.sh script is run via cron job every five minutes. And that script updates the cron job. They keep each other current at all times.

Rake Tasks

There are two rake tasks that build.sh calls: build and publish. The build task looks like this:

jekyll-repo/Rakefile
1
2
3
4
5
6
7
8
9
10
11
12
13
desc "Build site"
task :build do
  Rake::Task["convertkit"].invoke
  Rake::Task["podcast"].invoke
  Rake::Task["microblog"].invoke
  Rake::Task["webmentions"].invoke
  sh "JEKYLL_ENV=production bundle exec jekyll b"
  sh "git add --all .;git commit -m 'cron build';git push;"
  sh "rm -rf /path/to/_site/"
  sh "cp -pr /path/to/repo/_site/ /path/to/web/_site/"
  sh "curl -X POST https://micro.blog/ping?url=https://joebuhlig.com/microblog/feed.xml"
  Rake::Task["microblog_webmentions"].invoke
end

I won’t get into the other rake tasks here. That’s for another day. For this, the important pieces are the sh lines. All they do is build the site, commit the changes, delete the old _site directory, copy the newly generated _site directory to the published web directory, and then ping the Micro.Blog service to update my Micro.Blog feed. I do this last step to make sure the post is picked up right away. Basically, I became tired of waiting for it to propagate on its own.

The publish task has a different intent. It runs when there is no new commit found. Here’s what it looks like:

jekyll-repo/Rakefile
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
39
40
41
desc "Publish scheduled posts"
task :publish do
  update = false
  today = Time.now
  Dir['_posts/*','_microblogs/*'].each do |filename|
    file = File.open("#{filename}").each_line do |line|
      if line.start_with?("date: ")
        post_date = Time.parse(line.split("date:")[1])
        time_between = today - post_date
        if time_between < 300 and time_between > 0
          puts filename
          update = true
        end
      end
    end
    file.close
  end
  last_updated_file = File.open("_data/webmentions/last-id.txt")
  last_id = last_updated_file.read.strip
  payload = open("https://webmention.io/api/mentions.json?token=MYTOKEN&since_id=#{last_id}")
  if JSON.parse(payload.read)["links"].count > 0
    update = true
  end

  last_updated_file = File.open("_data/microblog/last-id.txt")
  last_id = last_updated_file.read.strip
  payload = open('http://micro.blog/feeds/joebuhlig.json')
  JSON.parse(payload.read)["items"].each do |item|
    url = URI.parse(item["url"])
    if url.host == "joebuhlig.com"
      if item['id'] > last_id
        update = true
        break
      end
    end
  end

 if update
    Rake::Task["build"].invoke
  end
end

There’s a lot more going on here, obviously. But here’s the gist of it. It searches the _posts and _microblogs directories for files that are dated within the last five minutes. If you recall, the cron job runs every five minutes. So it only needs to determine if there is a post with a date in the last five minutes. If no, the job is done and can wait for the next run five minutes later. If yes, it tells the build task to run.

You’ll also note a webmention section here. This script also checks to see if there are any new webmentions collected for my posts and microblogs. Again, if no, be done. If yes, run the build task.

Exclusions

In Jekyll, any file that starts with a period or an underscore is kept out of the _site directory by default. But there are others that I find need removed as well. This is pretty easy. I have the following added to the exclude item of my _config.yml file.

jekyll-repo/_config.yml
1
2
3
4
5
6
7
8
9
10
11
12
exclude:
  - Rakefile
  - Gemfile
  - Gemfile.lock
  - node_modules
  - vendor/bundle
  - vendor/cache
  - vendor/gems
  - vendor/ruby
  - config
  - Capfile
  - build.sh

Putting It All Together

In practice, all of this work allows very simple actions. To make an edit to my site, I merely need to commit the change. No need to build the site and copy it to the correct directory on the webserver.

This also means that to publish a new post or microblog in the future, I simply need to add a future date to the front matter. Within five minutes of the date passing, the article or microblog will post on its own. And that’s refreshing.

Planet Jekyll reposted this
cloudsh reposted this