coaching newsletter contact

Scheduling Posts and Microblogs With Jekyll

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

!/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

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

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

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

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.

Mon, Mar 09, 2020 07:51am CDT https://bhlg.us/55W1
#jekyll
2 likes
Planet Jekyll cloudsh
2 reposts
Planet Jekyll cloudsh

Want to see my August 2020 Bullet Journal setup?

Join 1,514 subscribers!

I'll send you the link to a six-minute overview video of my August 2020 spreads when you sign up for my newsletter, The Weekly Impulse.

Site Analytics

I use Fathom Analytics on this site because I care about your privacy. And if you ever want to see the stats collected and the data I see, check out the live analytics here.

Amazon Affiliate

joebuhlig.com is a participant in the Amazon Services LLC Associates Program, an affiliate advertising program designed to provide a means to earn fees when linking to Amazon.com and affiliated sites.

Disclaimer

Disclosure of Material Connection: Some of the links in the post above might be “affiliate links.” This means if you click on the link and purchase the item, I will receive an affiliate commission. Regardless, I only recommend products or services I use personally and believe will add value to my readers. I am disclosing this in accordance with the Federal Trade Commission’s 16 CFR, Part 255: “Guides Concerning the Use of Endorsements and Testimonials in Advertising.”

an analog mind in a digital world

👋 I'm Joe Buhlig. I strive to build productivity systems that stand the test of time and help me do more than check boxes. I'm here to help you do the same.

🎙 I read a lot of books and talk about it.

🐿 I can't focus on one thing for long, so I write a lot of code for an eclectic grouping of projects.

📓 And I'm a bit obsessed with finding non-proprietary solutions to digital problems. Thus, text files for the win! 🎉

MN U.S.A 1986-09-30
  • all
  • articles
  • code
  • likes
  • notes
  • photos
  • replies
  • reposts
  • steps
  • videos
coaching newsletter contact
© 2014-2023 by Joe Buhlig