Moving parts

Github: Code repository. We'll use 2 repos for this project. One contains the source, and one contains the HTML that we want published (using Github pages).

TravisCI: The continuous integration tool that we can leverage to build the site when the source gets updated.

Netlify CMS: The browser-based content management system that we'll integrate with Github to be able to create posts straight from a browser.

AWS Lambda: In order to authenticate the CMS with Github, we need an application in the middle to handle the OAuth2 handshake (and keep our secrets secret). We'll use a Lambda function that I wrote with the Serverless framework to do just that.

Static Site Generator - Hugo

I chose to leverage a static content site generator for this, as I wanted to keep things as simple as possible as well as ensure that the site will load fast and be cacheable.

The framework that I landed on was Hugo. Getting Hugo up and running is pretty straight-forward.

Follow the instructions in the quick-start guide, and you'll be ready for the next steps.

By the end of the quick-start, you should be able to test a working static site on your local machine.

This job will install a few packages, then run a couple scripts. The first script ensures that submodules are pointing to the branch HEAD, and the second script generates the site. Once the site is built, the new content is published to the publishing repo.

Head back over into the blog folder, remove the public folder and setup the submodule pointing to our publishing repo. Then push.

Setup API keys in Github

Head on over to Github, in the Settings page -> Developer settings, you'll find a menu for personal access tokens. Create a token, call it something meaningful (eg: TravisCI), and give the token access to everything in the 'repo' ACL section. Copy paste that somewhere for later.

Setup TravisCI

Head on over to Travis and login. Give Travis access to your repositories. Next, navigate to the blog repo in the Travis UI, and click on the settings tab. You want to add two variables: GITHUB_USER and GITHUB_API_KEY. The GITHUB_API_KEY is the key you just created, and the GITHUB_USER is your github username.

Checkpoint - What we have so far

If all went well, at this point we should have Travis automatically triggering every time something is pushed to the blog repo, and pushing changes after running Hugo to the publishing repo.

But why stop here? Let's create a UI to make this easier for non technical folks to update content.

Authentication

Before setting up the CMS, we'll want to setup an OAuth2 middle-man to hold our secrets.

There is a simpler version of this setup that trusts the nice folks at Netlify to do this for us, but being the paranoid person that I am, I'd rather run that myself.

Since I don't expect a lot of volume on this endpoint, unless I start receiving more than a million requests a month I can run this on AWS for free.

Before continuing, you'll want to create an AWS account if you don't already have one. You'll also want to create an IAM user with API access with full admin rights.

I usually setup an AWS profile in my ~/.aws/credentials file. See here for details.

Take note of the URLs that ends with /oauth/auth and /oauth/callback, we'll need those in a second.

Create an OAuth2 client id/secret in Github

Head on over to Github settings -> developer settings and create an OAuth2 app. The callback URL is the URL we just created (the one with /oauth/callback).

Take note of the OAuth2 client id and secret. We'll need those in a second.

Setup secrets in the AWS parameter store

Now let's configure our code to use the right parameters. Being good security conscious folks, we don't hardcode passwords or secrets anywhere and instead leverage an encrypted parameter store to store our secrets. In this case, we'll use the AWS SSM parameter store, which keeps things encrypted using KMS.

From the AWS console, head on over to either EC2 or Systems manager, then find Parameter store.

Content management

Netlify CMS is a content management system built as a React web app, which integrates into Github to allow you to edit content straight from your browser. Setting it up is as easy as dropping an HTML page into your site.

publish_mode:editorial_workflowbackend:name:githubrepo:marksteele/blogbase_url:https://RANDOMSTUFF.execute-api.us-east-1.amazonaws.com/prodauth_endpoint:/oauth/authsite_id:www.control-alt-del.orgmedia_folder:"static/images"public_folder:"/images"collections:-name:"blog"# Used in routes, e.g., /admin/collections/bloglabel:"Blog"# Used in the UIfolder:"content/blog"create:true# Allow users to create new documents in this collectionfields:# The fields for each document, usually in front matter-{label:"Layout",name:"layout",widget:"hidden",default:"blog"}-{label:"Title",name:"title",widget:"string"}-{label:"Publish Date",name:"date",widget:"datetime"}-{label:"Featured image",name:"thumbnail",widget:"image"}-{label:"Categories",name:"categories",widget:"list",default:["news"]}-{label:"Body",name:"body",widget:"markdown"}

Create the CMS webpage (~/blog/static/admin/index.html):

I've created a few custom widgets to add some more Hugo markdown shortcodes. Enjoy!

Add the newly create config/html to the blog source

cd ~/blog
git add .
git commit -m 'adding cms'
git push origin master

Give it a few minutes for Travis to push out the new build, then head on over to your published blog. If all went according to plan, you can hit your CMS athttps://YOURNAME.github.io/admin

You should then be able to login and start editing your files. Should look something like this:

Closing thoughts

So I lied a little bit in this post. In order to get the OAuth2 authentication to work, I had to edit the code of the CMS to fix a bug in how it was appending URL paths in the OAuth2 flow.

My site is running the branch that fixes the issue, so I assume it'll work when they merge the change upstream.

This post was written using the CMS, and I'm liking how easy it is to use.

I am serving this site using the CloudFront content delivery network, and I think I might write a small Lambda function to invalidate the CDN cache and trigger it every time the site gets published. Stay tuned!

-M

Update

Since writing this post, the PR has been merged to fix the OAuth2 flow, and that's working just fine.

I've also setup a Lambda function to be triggered by Travis whenever the build succeeds which invalidates CloudFront. Read more here. Here's the travis config.