Scarr: S3 + Cloudfront + ACM + Route53

There are a bunch of free/cheap options for hosting static sites (just html/css/js) out there: github pages, netlify, firebase hosting - but when I want to build a bulletproof static site "for real", my go-to toolset is S3 for hosting with Cloudfront caching in front of it.

My workflow for that usually looks like:

  1. Spend entirely too long picking a meaningful domain name like falafel.exposed
  2. Try to remember which registrar I decided I'd use for all my domains (namecheap, iwantmyname, gandhi, route53). Fail and pick one at random.
  3. Register the domain, then realize that one doesn't support apex domains (I demand falafel.exposed, not www.falafel.exposed like some peasant). Transfer the domain to route53 which does support apex domains.
  4. Create the falafel S3 bucket.
  5. Upload my flat files detailing the falafel conspiracy to S3
  6. Remember I need to enable S3 web hosting
  7. Create a new Cloudfront distribution pointing to that S3 bucket. Wait 45 minutes for this to finish.
  8. Realize I used the wrong bucket url format. Update Cloudfront and wait another 45 minutes.
  9. Remember I wanted TLS so that Big Falafel can't interfere with my traffic.
  10. Create an ACM certificate
  11. Verify the ACM certificate using route53. Spend 15 minutes futzing with route53's UI.
  12. Add the cert to the Cloudfront distribution and wait 45 minutes..
  13. Remember I need to configure an index file in S3. Go back and do that.
  14. Realize I got a Cloudfront setting wrong. Fix and wait 45 minutes.
  15. Same ^
  16. Look up how to set an apex domain in route53. Realize the web console doesn't support it yet. Spend 30 minutes futzing with the CLI tool instead.
  17. Cloudfront again.
  18. Finally get the truth up at https://falafel.exposed up, an entire afternoon later.

I figured that after a few times doing this (I've uncovered a lot of food-related conspiracies), I'd automate it. There are a few pre-existing tools for parts of this, but none I could find that did the whole thing from registration through uploading and Cloudfront invalidation.

So I built Scarr:

S3
Cloudfront
ACM
Route53
Redundant letter to prevent name collision

You use it like this:

$ scarr init -domain falafel.exposed -name falafelexposed
  Initializing...done
$ cd falafelexposed
$ vim scarr.yml # Edit a few fields here
$ echo "<html>The deadly secret of falafel</html>" > index.html
$ AWS_PROFILE=scarr scarr deploy
  ... a bunch of aws stuff happens automatically ...
$ curl https://falafel.exposed
  <html>The deadly secret of falafel</html>

What it's doing under the hood is:

  1. Registers the given domain through route53 (prompts to confirm this)
  2. Creates a TLS certificate through ACM
  3. Uses route53 DNS to validate that certificate
  4. Creates an S3 bucket
  5. Creates a Cloudfront distribution pointed to that S3 bucket using the ACM certificate
  6. Creates an apex dns record pointing to that Cloudfront
  7. Syncs the current directory to that S3 bucket and invalidates the Cloudfront cache.

It's also smart enough to detect if parts of this have already been done (eg you've already got the domain name in route53) and skip those parts. If you run the deploy command twice, all it does is sync the current directory to S3 and invalidate the cache.

Really, it's a glorified set of shell scripts wrapped in a single command. I wanted to be able to distribute it as a binary, though, so people could use it without needing to mess with ruby/python/node dependencies, so I took it as an opportunity to finally learn Go. It's generally a nice language - I'd forgotten how comfortable type-checking can be! On the other hand, I really missed ruby's built-in collection tools. The lack of generics was weird too.

The code's a hot mess. Everything's in the same package, there's global functions everywhere, and it's probably about as far from idiomatic Go as you can get, but it works. And at least it eschews the single-letter variables that seem so popular in Go. Surely that convention came from a falafelist.

Anyway, I'll try to clean it up if anyone takes an interest in it. I'm also open to expanding the functionality a bit if anyone has ideas that don't overly complicate the main use-case. PRs are welcome, although even lazy suggestions will get a friendly ear.

Anyway, the binary's at https://scarr.io/dist/scarr and the code's on github at github.com/kkuchta/scarr!