I thought I’d try and capture what it took to get this thing going. A static site generator is thing I haven’t toyed with in a long while and all the cool kids are playing with fancy new things I needed to learn. Combining Hugo, Gitlab, and Let’s Encrypt in order to automagically generate a secure site to throw content on wasn’t as straightforward as all the documents would have you believe. (But really, when is it ever as easy as the docs would lead you to believe?)
Making Hugo Work Locally
The Quick Start guide for hugo is solid and will get you running your first site right away. The steps below reflect me following the guide, adjusting a couple steps to leverage the minimo theme:
$ brew install hugo $ huge new site qmunic8 $ cd qmunci8 $ git init $ git submodule add https://github.com/MunifTanjim/minimo.git themes/minimo $ cp themes/minimo/exampleSite/config.toml .
From here I went to making changes to
config.toml to my tastes. I specifically avoided updating the
baseURL parameter at this point as there were no assets on a site yet. Leaving it alone for now worked best for testing purposes. I found that running the hugo server in a Visual Studio Code terminal window worked quite well. I could keep Safari & Code side-by-side while I worked and watch updates happen on the fly.
I set out to find a few image assests to make it more me, shoving them into
static in order to override the minimo theme assets. Learning about the
static directory here came in really handy later to get a LetsEncrypt certificate generated. This directory is exactly as it sounds: a place to through things that are not dynamically generated by Hugo, but are always available directly from dynamically generated pages. It’s very useful for replacing assets such as images and css that are in someone else’s theme.
Uploading and Generating a GitLab Page
The Hosting on GitLab guide was a great help. I just skipped the repeat
git init step, but otherwise followed this to the letter and in a few minutes my site was up on gitlab.io.
From this point forward, making updates to the site are as simple as a commit and push away. When I push to the repo, GitLab’s CI solution sucks in a docker image, pulls in minimo as a git submodule, executes Hugo to generate the site, then deploys the artifact to their webserver. Pretty damn nifty.
Using My Own Domain
Sure, I could just use the tekbuddha.gitlab.io domain, but nah. I’ve got a couple domains I still hang on to I want to leverage. Glancing over the Add your custom domain instructions, this is all really easy. I just need an A record, a CNAME, and a TXT record in my domain. I update these records accordingly in the Hover portal and in a couple minutes I’m able to pull up the site from GitLab by my own domain. 💥
Note: I did have to uncheck the Force domains with SSL certificates to use HTTPS box when adding the domain in GitLab cause I didn’t have a certificate yet, but that was about to change.
Hoop Jumping For a Certificate
Now that I have a site working on my own domain, I want a certificate for it. Thanks to the fine folks at Let’s Encrypt I can get one for free but because I don’t have a shell on the webserver itself there are a special hoops to jump through.
The EFF provides a tool called certbot which can help automatically generate a certificate for your site. The problem is it wants to run directly on the webserver your site is hosted on. But hidden within the certbot source is a tool called
letsencrypt-auto which can be used to generate a token you put on your site that LetsEncrypt can fetch to determine your site is really yours and then generate a certificate for you.
I was momentarily defeated though upon the first run:
$ ./letsencrypt-auto Requesting to rerun ./letsencrypt-auto with root privileges... Password: WARNING: certbot-auto support for this macOS is DEPRECATED! Please visit certbot.eff.org to learn how to download a version of Certbot that is packaged for your system. While an existing version of certbot-auto may work currently, we have stopped supporting updating system packages for your system. Please switch to a packaged version as soon as possible.
Well damn. After some quick searching I find that using the
--debug flag is helpful to get around that deprecation warning. Let’s give that a shot:
$ ./letsencrypt-auto --debug Requesting to rerun ./letsencrypt-auto with root privileges... Bootstrapping dependencies for macOS... (you can skip this with --no-bootstrap) Using Homebrew to install dependencies... Error: Running Homebrew as root is extremely dangerous and no longer supported. As Homebrew does not drop privileges on installation you would be giving all build scripts full access to your system.
😒 So apparently this thing wants to install its dependencies via Homebrew and I can skip their installation with the
--no-bootstrap flag. What are those dependencies? I quickly grep the package install commands out of
letsencrypt-auto and get to work:
$ brew install augeas $ sudo pip install virtualenv $ sudo pip install hashin
I’m confident this wasn’t the right way (well, the clean way, anyhow) but it got me unblocked:
$ ./letsencrypt-auto certonly -a manual -d qmunic8.com --debug --no-bootstrap Requesting to rerun ./letsencrypt-auto with root privileges... Creating virtual environment... Installing Python packages... Installation succeeded. Saving debug log to /var/log/letsencrypt/letsencrypt.log Plugins selected: Authenticator manual, Installer None Enter email address (used for urgent renewal and security notices) (Enter 'c' to cancel):
Success! At this point, I followed the script’s instructions, supplying my email address and acknowledging a couple of items, eventually resulting in the following output:
------------------------------------------------------------------------------- Create a file containing just this data: g1gj0cFzBBHFBtWO-RcsgpX52UexqDIgrE0VlZc3qwRZexKU-hQ-vWhSSN58 And make it available on your web server at this URL: http://qmunic8.com/.well-known/acme-challenge/RcsgpX52UexqDIgrE0VlZc3qwRZexKU ------------------------------------------------------------------------------- Press Enter to Continue
Note this is a fudged challenge token and file name for example purposes…
I need to get this file up on the site so LetsEncrypt can verify I actually control my site. Remember that
static lesson I was mentioning earlier? This is a perfect use case:
$ mkdir -p static/.well-known/acme-challenge/RcsgpX52UexqDIgrE0VlZc3qwRZexKU $ echo "g1gj0cFzBBHFBtWO-RcsgpX52UexqDIgrE0VlZc3qwRZexKU-hQ-vWhSSN58 " > static/.well-known/acme-challenge/RcsgpX52UexqDIgrE0VlZc3qwRZexKU/index.html
Commit, push, and verify I see the challenge code when I query the expected URL.
$ curl http://qmunic8.com/.well-known/acme-challenge/RcsgpX52UexqDIgrE0VlZc3qwRZexKU/index.html g1gj0cFzBBHFBtWO-RcsgpX52UexqDIgrE0VlZc3qwRZexKU-hQ-vWhSSN58
Now that I’ve confirmed the challenge code is retriveable, I went back to my terminal window running the
letsencrypt-auto process and happily hit the Enter key to see what happened next.
Press Enter to Continue Waiting for verification... Cleaning up challenges IMPORTANT NOTES: - Congratulations! Your certificate and chain have been saved at: /etc/letsencrypt/live/qmunic8.com/fullchain.pem Your key file has been saved at: /etc/letsencrypt/live/qmunic8.com/privkey.pem Your cert will expire on 2018-09-14. To obtain a new or tweaked version of this certificate in the future, simply run letsencrypt-auto again. To non-interactively renew *all* of your certificates, run "letsencrypt-auto renew" - Your account credentials have been saved in your Certbot configuration directory at /etc/letsencrypt. You should make a secure backup of this folder now. This configuration directory will also contain certificates and private keys obtained by Certbot so making regular backups of this folder is ideal. - If you like Certbot, please consider supporting our work by: Donating to ISRG / Let's Encrypt: https://letsencrypt.org/donate Donating to EFF: https://eff.org/donate-le
MOAR SUCCESS! Now I have a certificate (with a chain) and key I can supply to GitLab and make my site SSL friendly. I cat the content of both these files and place their contents in the Certificate (PEM) and Key (PEM) fields respectively on the domain page within GitLab, save my additions, and presto! it’s ready to go.
$ curl -vX HEAD https://qmunic8.com Warning: Setting custom HTTP method to HEAD with -X/--request may not work the Warning: way you want. Consider using -I/--head instead. * Rebuilt URL to: https://qmunic8.com/ * Trying 126.96.36.199... * TCP_NODELAY set * Connected to qmunic8.com (188.8.131.52) port 443 (#0) * ALPN, offering h2 * ALPN, offering http/1.1 * Cipher selection: ALL:!EXPORT:!EXPORT40:!EXPORT56:!aNULL:!LOW:!RC4:@STRENGTH * successfully set certificate verify locations: * CAfile: /etc/ssl/cert.pem CApath: none * TLSv1.2 (OUT), TLS handshake, Client hello (1): * TLSv1.2 (IN), TLS handshake, Server hello (2): * TLSv1.2 (IN), TLS handshake, Certificate (11): * TLSv1.2 (IN), TLS handshake, Server key exchange (12): * TLSv1.2 (IN), TLS handshake, Server finished (14): * TLSv1.2 (OUT), TLS handshake, Client key exchange (16): * TLSv1.2 (OUT), TLS change cipher, Client hello (1): * TLSv1.2 (OUT), TLS handshake, Finished (20): * TLSv1.2 (IN), TLS change cipher, Client hello (1): * TLSv1.2 (IN), TLS handshake, Finished (20): * SSL connection using TLSv1.2 / ECDHE-RSA-AES128-GCM-SHA256 * ALPN, server accepted to use h2 * Server certificate: * subject: CN=qmunic8.com * start date: Jun 16 22:20:15 2018 GMT * expire date: Sep 14 22:20:15 2018 GMT * subjectAltName: host "qmunic8.com" matched cert's "qmunic8.com" * issuer: C=US; O=Let's Encrypt; CN=Let's Encrypt Authority X3 * SSL certificate verify ok. * Using HTTP2, server supports multi-use * Connection state changed (HTTP/2 confirmed) * Copying HTTP/2 data in stream buffer to connection buffer after upgrade: len=0 * Using Stream ID: 1 (easy handle 0x7fbba1800a00) > HEAD / HTTP/2 > Host: qmunic8.com > User-Agent: Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0) > Accept: */* > Referer: > * Connection state changed (MAX_CONCURRENT_STREAMS updated)! < HTTP/2 200 < accept-ranges: bytes < cache-control: max-age=600 < content-type: text/html; charset=utf-8 < expires: Sat, 16 Jun 2018 23:36:45 UTC < last-modified: Sat, 16 Jun 2018 23:15:19 GMT < vary: Origin < content-length: 6926 < date: Sat, 16 Jun 2018 23:26:45 GMT < * transfer closed with 6926 bytes remaining to read * Closing connection 0 * TLSv1.2 (OUT), TLS alert, Client hello (1): curl: (18) transfer closed with 6926 bytes remaining to read
That’s it, right? Hot damn.