Worm with Glasses

Coding • DevOps • Personal

Aug 13, 2017

Managing Site Content with Sub

No self respecting developer will tolerate writing out the same commands over and over.

Automate All the Things!

With Jekyll building the static files, I needed a way to:

  • create draft posts
  • serve drafts via localhost to review
  • publish a draft as a full post
  • build the site
  • deploy to DigitalOcean

After manually running shell commands and manually editing files, I had to automate the workflow.

I use sub to organize the various scripts that handle each part of the process.

Create Draft Posts

First step was to create a template.md file with all the optional Front Matter a post might need.

---
title:

# # Rest are optional
# categories:
# tags:
# permalink:
# excerpt: # Markdown description before the story
# description: # SEO `<meta>` description (requires an `excerpt`)
# header:
#  image: # Path to the full image for the size (1280x...)
#  image_description: # Custom `alt` tag
#  caption: # Photo credit (can be written in markdown)
#  teaser: # Path to teaser image (roughly 500x300)
#
#  # Hero video image
#  video:
#    id: 
#    provider:
#
#  # An image with text overlay and call to action button
#  overlay_image: # Path to the full image size
#  overlay_filter: 0.5 # opacity between 0.0 and 1.0 (or full `rgba(255, 0, 0, 0.5)`)
#  cta_label: # Call to action button label
#  cta_url: # URL the call to action button goes to
#  overlay_color: # Can be used rather than `overlay_image` as #ccc
#
# NOTE: https://mmistakes.github.io/minimal-mistakes/docs/helpers/
#       https://mmistakes.github.io/minimal-mistakes/docs/utility-classes/
---

You’ll notice that only title is required. Everything is commented out. Also, there’s no date attribute since this is a draft.

When I begin writing a draft I run:

site draft whatever-the-post-slug-will-be

which runs:

#!/bin/sh
set -u
set -e

cp _drafts/template.md _drafts/${1}.md
exec $EDITOR _drafts/${1}.md

A simple script to copy the template into the _drafts/ folder with the slug name and then fire up my editor (NeoVim.)

Previewing Drafts on Localhost

Jekyll can preview drafts on the localhost. I run:

site serve

which executes:

#!/bin/sh

set -x
set -e

jekyll serve -D --watch

in a separate tmux pane. Jekyll is configured to watch for changes and rebuild the draft for preview.

Publishing

Once I’m happy with a draft it’s time to publish it (meaning promoting it from _drafts/ into _posts/.)

site publish whatever-the-post-slug-will-be

Which runs:

#! /usr/bin/env ruby
#
# Copy the file from the drafts folder, add the current date, and update the
# filename.  Remove any commented out lines from the front matter.
#

now = Time.now
draft = File.basename(ARGV[0], '.md')
draft_filename = File.join('_drafts', "#{draft_filename}.md")
post_filename = File.join('_posts', "#{now.strftime('%Y-%m-%d')}-#{draft}.md")
post = File.open(post_filename, 'w')

state = :start

open(draft_filename).readlines.each do |line|
  if (state == :start || state == :in_front_matter) && line =~ /^---/
    post.puts line

    if state == :start
      post.puts "date: #{now.strftime('%Y-%m-%d %H:%M:%S %:z')}"

      state = :in_front_matter
    else
      state = :body
    end
  elsif state == :in_front_matter && (line =~ /^#/ || line =~ /^\s$/)
    # Skip all the comment lines in the front matter
    next
  else
    post.puts line
  end
end

post.close

File.unlink(draft_filename) if File.exist?(post_filename)

This Ruby script:

  1. Adds the current date attribute to the Front Matter
  2. Removes all the commented out lines in the Front Matter
  3. Moves the file from the _drafts folder to the _posts folder and prefixes the current date to the file.

Deploy Updates to Production

With everything ready, it’s time to deploy the changes to the [site]({{ .Site.BaseURL }}). One last script:

site deploy

running:

#!/bin/sh

$_SITE_ROOT/bin/site build
rsync -crvz --delete-after --delete-excluded  _site/ wormbytes:/var/www/wormbytes.ca/

First we build the site:

#!/bin/sh

set -u
set -e

jekyll build

and then copy the _site/ directory up to the DigitalOcean production server, deleting any files that have been removed and updating any new files.

Aug 13, 2017

Podcast Subscriptions in Overcast

My brother-in-law asked me what podcasts I’m listening to and I didn’t have a good way of giving him my Overcast subscription list (since it’s not easy to share an OPML export.) Below is my current (as of August 2017) subscription list.

I 🌟’d my favourite shows.

Aug 12, 2017

Raising a Website from the Dead

Monty Python and the Holy Grail: Bring Out Your Dead

Over the past week I’ve been putting this website back together (using Jekyll and Minimal Mistakes.)

It’s been an adventure.

Back in 2005, I built WormBytes using a custom built static website generator written in Perl’s Template Toolkit. After a couple of years I had moved on to Ruby and I stopped updating the site.

In September of 2009 my wife, Roselle Kaes, and I found out we were to be parents. I felt this should be documented so I resurrected the site on WordPress.

The WordPress site was a short lived experiment (less than eight months!)

That’s where the site sat until 2016 when the super old computer I was using as a server (a machine that used to run my ISP Flarenet and which had been sitting in my parent’s basement) finally died.

WormBytes as a web presence ceased to exist. :(

A former coworker at ePublishing, Christopher Coleman, asked me to publish some of the software development practises we’ve developed at ePublishing. It’s a brilliant idea, but I had no where to put the articles.

Until now.

Aug 11, 2017

Let's Encrypt and Nginx on Ubuntu

A combination lock resting on a laptop keyboard

Configuring nginx to use SSL certificates from Let’s Encrypt using the Webroot method isn’t hard, but there are a few steps to make it all work. We assume:

  • you have only one website setup in nginx and that any additional sites will also use Let’s Encrypt for their SSL certificates.
  • HTML is served from /var/www/example.com

Prerequisites

  • Ubuntu 16.04
  • nginx
  • You must own or control the registered domain name that you wish to use the certificate with.
  • A DNS A Record that points your domain to the public IP address of your server. Let’s Encrypt requires this to validate that you own the domain. You will need an A Record for both www.example.com and example.com if you want to support both domains.

Step 1 - Installing Certbot

The easiest way to request a SSL certificate from Let’s Encrypt is to use the certbot. The Certbot developers have their own [Ubuntu] software repository with update-to-date versions of the software.

First add the repository:

sudo apt-get install software-properties-common
sudo add-apt-repository ppa:certbot/certbot
sudo apt-get update

You’ll need to press ENTER to accept the certbot repository.

Now install Certbot:

sudo apt-get install certbot

The certbot Let’s Encrypt client is now ready.

Step 2 - Create Nginx Configs

Let’s Encrypt uses a challenge system to ensure the domain for which the SSL certificate is about to be issued is controlled by the requester. To avoid duplicating the logic in every virtual host configuration (as we’re setting up both example.com and www.example.com) we’ll create a snippet.

sudo mkdir -p /etc/nginx/snippets/

Create /etc/nginx/snippets/letsencrypt.conf containing:

location ^~ /.well-known/acme-challenge/ {
	default_type "text/plain";
	root /var/www/letsencrypt;
}

Create the ACME challenge folder (as referenced above):

sudo mkdir -p /var/www/letsencrypt/.well-known/acme-challenge

We need to create the server specific dhparam.pem file. This will take a while:

sudo apt-get install openssl
sudo openssl dhparam -out /etc/ssl/certs/dhparam.pem 2048

Now, within /etc/nginx/conf.d we’ll create the global SSL settings. Create /etc/nginx/conf.d/ssl.conf containing:

ssl_session_timeout 1d;
ssl_session_cache shared:SSL:50m;
ssl_session_tickets off;

ssl_protocols TLSv1.2;

# Use the "Modern" cipher suite recommended by Mozilla
# https://wiki.mozilla.org/Security/Server_Side_TLS#Modern_compatibility
ssl_ciphers
'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256';
ssl_ecdh_curve secp384r1;
ssl_prefer_server_ciphers on;
ssl_dhparam /etc/ssl/certs/dhparam.pem;

ssl_stapling on;
ssl_stapling_verify on;

# Enable HTST to ensure communication is only over SSL
add_header Strict-Transport-Security "max-age=15768000; includeSubdomains;
preload";
add_header X-Frame-Options DENY;
add_header X-Content-Type-Options nosniff;

Step 3 - Configure Domain to Respond to ACME Challenge

As we don’t have any SSL certificates yet, we need to configure our domain to respond to the ACME challenges. Assuming your nginx virtual server configuration is at /etc/nginx/sites-available/example.com.conf add:

include /etc/nginx/snippets/letsencrypt.conf;

between your server { ... } blocks.

Enable the site:

sudo rm /etc/nginx/sites-enabled/default
sudo ln -s /etc/nginx/sites-available/example.com.conf /etc/nginx/sites-enabled/example.com.conf

And then reload nginx:

sudo systemctl reload nginx

Step 4 - Obtaining an SSL Certificate

Request the certificate (don’t forget to replace with your own email address):

certbot certonly --webroot --agree-tos --no-eff-email --email YOUR@EMAIL.COM -w /var/www/letsencrypt -d www.example.com -d example.com

It will save the files into /etc/letsencrypt/live/www.example.com/

Note: The --no-eff-flag opts out of signing up for the EFF mailing list.

Step 5 - Setup HTTPS-Only Virtual Hosts

Now that we have the SSL certificates, switch your domain to HTTPS. Edit /etc/nginx/sites-available/domain.com.conf and replace the HTTP server configs with:

## http://example.com redirects to https://example.com
server {
	listen 80;
	listen [::]:80;
	server_name example.com;

	include /etc/nginx/snippets/letsencrypt.conf;

	location / {
		return 301 https://example.com$request_uri;
	}
}

## http://www.example.com redirects to https://www.example.com
server {
	listen 80 default_server;
	listen [::]:80 default_server ipv6only=on;
	server_name www.example.com;

	include /etc/nginx/snippets/letsencrypt.conf;

	location / {
		return 301 https://www.example.com$request_uri;
	}
}

## https://example.com redirects to https://www.example.com
server {
	listen 443 ssl;
	listen [::]:443 ssl;
	server_name example.com;

	ssl_certificate /etc/letsencrypt/live/www.example.com/fullchain.pem;
	ssl_certificate_key /etc/letsencrypt/live/www.example.com/privkey.pem;
	ssl_trusted_certificate /etc/letsencrypt/live/www.example.com/fullchain.pem;

	location / {
		return 301 https://www.example.com$request_uri;
	}
}

## Serves https://www.example.com
server {
	server_name www.example.com;
	listen 443 ssl default_server;
	listen [::]:443 ssl default_server ipv6only=on;

	ssl_certificate /etc/letsencrypt/live/www.example.com/fullchain.pem;
	ssl_certificate_key /etc/letsencrypt/live/www.example.com/privkey.pem;
	ssl_trusted_certificate /etc/letsencrypt/live/www.example.com/fullchain.pem;

  # The rest of your previous HTTP virtual server configuration.  Below is an
  # example:
	root /var/www/example.com;
	index index.html;
	location / {
		try_files $uri $uri/ =404;
	}
}

Finally, reload nginx:

sudo systemctl reload nginx

Note that http://example.com redirects to https://example.com (which redirects to https://www.example.com) because redirecting to https://www.example.com directly would be incompatible with HSTS.

Step 6 - Automatic Renewal using Cron

Certbot can renew all certificates that expire within 30 days. We’ll create a cronjob that automatically updates the certificates (and restarts nginx to pick up the changes.)

Let’s try a dry-run to ensure that everything is working:

sudo certbot renew --dry-run

Create /usr/local/sbin/letsencrypt.sh containing:

#!/bin/bash
systemctl reload nginx

# If you have other services that use the certificates add reloads here:

Make it executable:

sudo chmod +x /usr/local/sbin/letsencrypt.sh

Edit the root’s cron:

sudo crontab -e

And add:

20 3 * * * certbot renew --noninteractive --renew-hook /usr/local/sbin/letsencrypt.sh

Conclusion

And that’s it! If all went well you should see your site at https://www.example.com/!

Apr 15, 2014

Checking for Cookie Deletion by a Rails Controller with RSpec

Today I needed to prove that my Rails controller method deleted a cookie. After Googling for the answer (without success), I came up with the following.

Within your controller:

cookies.delete('cookie-to-delete')

In your controller spec you can check that the cookie was deleted with:

expect(response.cookies).to include('cookie-to-delete' => nil)

Rails sets the cookies to be deleted to a value of nil. By checking for the presence of both the cookie name with a value of nil, you can ensure your controller code deleted the cookie.

Apr 7, 2010

Mar 11, 2010

Nov 14, 2009

It's a Little Girl!

Today’s ultrasound went great! This was our first chance to see our baby as a baby (and not as a blob of blurry pixels.) The technician was great, and spent nearly 20 minutes with us taking all sorts of pictures. The very first photo told us that we’re having a girl.

I enjoyed this ultrasound as Natalie (the name we decided on for the baby) was very active and moving around. It was so fascinating watching her wave, and kick, and blink, and suck her thumb.

Only five more months to go. Can’t wait!

Sep 23, 2009

Ottawa Was a Blast

Our trip to Ottawa was a blast.  Rosey has posted a bunch of our photos online. It was nice to get away for a while. The drive up took 7 hours, but that’s because we stopped a lot (all the rest stops along the 401.) Once we got past Toronto, the drive actually wasn’t too bad. The scenery was absolutely beautiful, and it was great seeing the names of places I had only ever seen on a map.

Our hotel was right beside Byword Market, so we were able to walk everywhere we wanted to go. I really appreciated that as I did not want to drive.

Our first day of walking was to the National Art Galley. We walked past the American Embassy, which was very odd.  It looks like a prison with all the barred windows and gates. The funniest part was seeing the protesters on the other side of the road.

The art gallery was great.  Very big.  It took us a nearly three hours to go through the entire building. The rest of the week was spent exploring Byword Market and the shops around it.

On our last day we walked up to Parliament Hill and took a bunch of pictures. We have some great shots of the various statues, and the cat sanctuary.

Our drive back was less eventful.  Only took six hours. We made it back in time to get the cat from the vet before they closed, so our whole family is now back home.

Definitely was well worth the money, and it was a great way to escape what had been a rather trying series of months.

Sep 16, 2009

48 Hour Emotional Rollercoaster

The past 48 hours have been the most trying of my life.

It started Saturday night when Rosey yelled for me to help her in the bathroom. She was bleeding. A lot. Unfortunately, there was nothing I could do other than be there for support. We spent an hour in the bathroom and were positive that Rosey had miscarried. Finally, once all the bleeding had stopped, I packed her into the car and drove down to the local hospital.

We waited three hours for a doctor to examine Rosey and in that time we ran through the gamut of emotions. At 3am Rosey was examined, and the doctors were fairly confident that she had, in fact, had a miscarriage. They scheduled an ultrasound for Monday to confirm that everything had passed and that she was not at any risk of an infection.

Obviously, we were both completely devastated. Rosey’s pregnancy so far had been extremely difficult with her sleeping 18+ hours a day and unable to eat.  (She had lost nearly 10 lbs.) But we didn’t want it to end like that.

We talked a lot about our situation on Sunday, and Rosey was feeling physically better, which was at least a small positive.

On Monday afternoon, we went in for the ultrasound and received the surprise of a lifetime…

Our baby was still there! Heart still beating. It had not even moved!

Rosey was hysterical.

I was stunned.

What had just happened? How could Rosey lose that much blood and clots and everything be okay? All we had was questions, and no answers. The medical centre quickly scheduled us in for an appointment the next day. There they performed another set of tests on Rosey and everything looked good. No signs of any trauma.

The doctor were not able to explain what exactly had happened, and could not rule out a potential miscarriage in the future, but at the moment the baby is doing well, and Rosey is feeling a lot better. They’ve prescribed new medication to help alleviate her morning sickness and allow her to eat.

It’s a nerve wracking time for us now. We overjoyed that we hadn’t lost the baby, but so scared about what might happen. All we can do is try and stay positive. Whatever happens will happen and there’s not much we can do to prevent it.

Hopefully our trip to Ottawa this weekend will take our mind off the potential bad. I just want the rest of the pregnancy to go smoothly. No more scares. One normal, health baby at the end.