Copy edits and added TOC to all posts
This commit is contained in:
parent
4008ef6f8c
commit
9698f2bfca
5 changed files with 35 additions and 211 deletions
159
build_site.go
159
build_site.go
|
|
@ -1,159 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
"time"
|
||||
)
|
||||
|
||||
func main() {
|
||||
build()
|
||||
}
|
||||
|
||||
// In this example `messageId` is a value that
|
||||
// needs to be stored in an environment variable to be
|
||||
// used by the command that's going to be executed
|
||||
func build() error {
|
||||
binary, err := exec.LookPath("hugo")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
modePtr := flag.String("mode", "", "set application mode to either 'serve' or 'build'")
|
||||
|
||||
flag.Parse()
|
||||
|
||||
cmd := exec.Command(binary, *modePtr)
|
||||
env := os.Environ()
|
||||
|
||||
// lat, lon, currentLocationName := getCurrentLocation()
|
||||
//env = append(env, fmt.Sprintf("HUGO_PARAMS.CurrentLat=%f", lat))
|
||||
//env = append(env, fmt.Sprintf("HUGO_PARAMS.CurrentLon=%f", lon))
|
||||
//env = append(env, fmt.Sprintf("HUGO_PARAMS.CurrentLocationName=%s", currentLocationName))
|
||||
|
||||
// TEMP!
|
||||
env = append(env, fmt.Sprintf("HUGO_PARAMS.CurrentLocationName=%s", "Bellingham, WA"))
|
||||
env = append(env, "HUGO_PARAMS.Subtitle=He's Very Robust")
|
||||
env = append(env, "HUGO_PARAMS.Logo=img/avatar-icon.png")
|
||||
env = append(env, "HUGO_PARAMS.Favicon=img/favicon.ico")
|
||||
env = append(env, "HUGO_PARAMS.Rss=true")
|
||||
env = append(env, "HUGO_PARAMS.ShowRelatedPosts=true")
|
||||
|
||||
cmd.Env = env
|
||||
|
||||
cmdOut, _ := cmd.StdoutPipe()
|
||||
cmdErr, _ := cmd.StderrPipe()
|
||||
|
||||
startErr := cmd.Start()
|
||||
if startErr != nil {
|
||||
return startErr
|
||||
}
|
||||
|
||||
// read stdout and stderr
|
||||
stdOutput, _ := ioutil.ReadAll(cmdOut)
|
||||
errOutput, _ := ioutil.ReadAll(cmdErr)
|
||||
|
||||
fmt.Printf("STDOUT: %s\n", stdOutput)
|
||||
fmt.Printf("ERROUT: %s\n", errOutput)
|
||||
|
||||
err = cmd.Wait()
|
||||
return err
|
||||
}
|
||||
|
||||
type point struct {
|
||||
Lat float64 `json:"lat"`
|
||||
Lon float64 `json:"lon"`
|
||||
}
|
||||
|
||||
func getCurrentLocation() (lat, lon float64, currentLocationName string) {
|
||||
//{{ $.Scratch.Set "description" (printf "I'm current in %s" $place.properties.label) }}
|
||||
url := "https://adriano.fyi/current_location"
|
||||
|
||||
client := http.Client{
|
||||
Timeout: time.Second * 10, // Timeout after 2 seconds
|
||||
}
|
||||
|
||||
req, err := http.NewRequest(http.MethodGet, url, nil)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
res, getErr := client.Do(req)
|
||||
if getErr != nil {
|
||||
log.Fatal(getErr)
|
||||
}
|
||||
|
||||
if res.Body != nil {
|
||||
defer res.Body.Close()
|
||||
}
|
||||
|
||||
body, readErr := ioutil.ReadAll(res.Body)
|
||||
if readErr != nil {
|
||||
log.Fatal(readErr)
|
||||
}
|
||||
|
||||
p := point{}
|
||||
jsonErr := json.Unmarshal(body, &p)
|
||||
if jsonErr != nil {
|
||||
log.Fatal(jsonErr)
|
||||
}
|
||||
|
||||
lat, lon = p.Lat, p.Lon
|
||||
currentLocationName = getCurrentLocationName(lon, lat)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
type feature struct {
|
||||
PlaceName string `json:"place_name"`
|
||||
}
|
||||
|
||||
type peliasResponse struct {
|
||||
Features []feature `json:"features"`
|
||||
}
|
||||
|
||||
func getCurrentLocationName(lon, lat float64) (locationName string) {
|
||||
client := http.Client{
|
||||
Timeout: time.Second * 2, // Timeout after 2 seconds
|
||||
}
|
||||
|
||||
url := fmt.Sprintf("https://api.mapbox.com/geocoding/v5/mapbox.places/%f,%f.json?access_token=%s&types=locality,place", lon, lat, os.Getenv("HUGO_MAPBOX_API_KEY"))
|
||||
req, err := http.NewRequest(http.MethodGet, url, nil)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
res, getErr := client.Do(req)
|
||||
if getErr != nil {
|
||||
log.Fatal(getErr)
|
||||
}
|
||||
|
||||
if res.Body != nil {
|
||||
defer res.Body.Close()
|
||||
}
|
||||
|
||||
body, readErr := ioutil.ReadAll(res.Body)
|
||||
if readErr != nil {
|
||||
log.Fatal(readErr)
|
||||
}
|
||||
|
||||
r := peliasResponse{}
|
||||
jsonErr := json.Unmarshal(body, &r)
|
||||
if jsonErr != nil {
|
||||
log.Fatal(jsonErr)
|
||||
}
|
||||
|
||||
if len(r.Features) == 0 {
|
||||
log.Printf("Unable to fetch location for coordinates: %f,%f", lon, lat)
|
||||
} else {
|
||||
locationName = r.Features[0].PlaceName
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
27
config.yaml
27
config.yaml
|
|
@ -1,27 +0,0 @@
|
|||
baseurl: https://adriano.fyi
|
||||
DefaultContentLanguage: en
|
||||
title: Adriano Caloiaro
|
||||
theme: adriano
|
||||
metaDataFormat: yaml
|
||||
pygmentsStyle: trac
|
||||
pygmentsUseClasses: true
|
||||
pygmentsCodeFences: true
|
||||
pygmentsCodefencesGuessSyntax: true
|
||||
publishDir: /var/www/adriano.fyi
|
||||
ignoreErrors: ["error-remote-getjson"]
|
||||
|
||||
params:
|
||||
# Configure params in build_site.go
|
||||
author:
|
||||
name: Adriano
|
||||
email: homepage@adriano.fyi
|
||||
github: acaloiaro
|
||||
|
||||
menu:
|
||||
main:
|
||||
- name: About Me
|
||||
url: /aboutme/
|
||||
weight: 1
|
||||
- name: Projects
|
||||
url: /projects/
|
||||
weight: 0
|
||||
|
|
@ -7,6 +7,8 @@ url: /aboutme
|
|||
|
||||
I live in my RV and spend most of my time running, climbing, cycling, snowboarding, and hiking. Pretty much anything in the outdoors keeps me happy. When I'm not doing any of those things, I'm probably writing software; see [my projects page](/projects/) for some of the things I like to work on.
|
||||
|
||||
You can email me at `hello@adriano.fyi`.
|
||||
|
||||
This is how I've been keeping active lately
|
||||
|
||||
{{< rawhtml >}}
|
||||
|
|
|
|||
|
|
@ -14,24 +14,24 @@ aliases:
|
|||
- /post/2023/2023-06-17-lets-encrypt-with-hashicorp-nomad/
|
||||
---
|
||||
|
||||
# Summary
|
||||
## Overview
|
||||
|
||||
I'm always looking for the easy way. Now certbot already makes retrieving TLS certificates from [Let's Encrypt](https://letsencrypt.org/) easy. But it's getting those certificates "into production" that tends to be less easy. This is the easy way to get Let's Encrypt TLS certificates into production with Hashicorp's Nomad.
|
||||
I'm always looking for the easy way. Certbot already makes retrieving TLS certificates from [Let's Encrypt](https://letsencrypt.org/) easy. But it's getting those certificates "into production" that tends to be less easy. This is the easy way to get Let's Encrypt TLS certificates into production with Hashicorp's Nomad.
|
||||
|
||||
This is an overview of what we'll be doing.
|
||||
This is an overview of what we'll be doing:
|
||||
- Using Nomad's [docker driver](https://developer.hashicorp.com/nomad/docs/drivers/docker) to run docker container jobs.
|
||||
- Using a [`cerbot` docker image](https://hub.docker.com/r/inetsoftware/certbot-dns-hetzner) that performs a [DNS-01 Challenge](https://letsencrypt.org/docs/challenge-types/) using Hetzner's DNS API.
|
||||
- Using a [cerbot docker image](https://hub.docker.com/r/inetsoftware/certbot-dns-hetzner) that performs a [DNS-01 Challenge](https://letsencrypt.org/docs/challenge-types/) using Hetzner's DNS API.
|
||||
- Using Nomad [host volumes](https://developer.hashicorp.com/nomad/docs/configuration/client#host_volume-block) to share certificates between nomad tasks.
|
||||
- Using Nomad [lifecycle block](https://developer.hashicorp.com/nomad/docs/job-specification/lifecycle) to initialize certificates before Nginx startup.
|
||||
- Using Nginx as a reverse proxy/SSL termination
|
||||
|
||||
Here is what we won't be doing.
|
||||
Here is what we won't be doing:
|
||||
- Setting up Nomad from scratch
|
||||
- Reviewing how to set up Nomad [host volumes](https://developer.hashicorp.com/nomad/docs/configuration/client#host_volume-block)
|
||||
- Setting up Nomad jobs that Nginx can reverse proxy to. However, the example nginx configuration provides an example that uses Nomad service discovery
|
||||
- Auto-configuring DNS records to point at the Nomad client running our nginx job
|
||||
|
||||
If anything mentioned here is not _exactly_ the same as your stack, you'll still be able to take a lot away from this general approach. Stick around. For example, let's say your not using Hetzner, but AWS Route 53 for DNS -- there are certbot images that can create Route 53 records.
|
||||
If anything mentioned here is not _exactly_ the same as your stack, don't worry because you'll be able to use the general approach on different stacks. For example, let's say you're not using Hetzner, but AWS Route 53 for DNS -- you will simply change the `certbot` docker image from Hetzner to the [official cerbot route53 image](https://hub.docker.com/r/certbot/dns-route53).
|
||||
|
||||
|
||||
| Component | Details |
|
||||
|
|
@ -40,25 +40,25 @@ If anything mentioned here is not _exactly_ the same as your stack, you'll still
|
|||
| **Reverse Proxy/TLS** | [Nginx](https://www.nginx.com/) -- the old standby |
|
||||
| **Job Scheduling** | [Hashicorp Nomad](http://nomadproject.io) -- no frills, simple, job scheduling |
|
||||
|
||||
If you're here, you probably familiar with each of these, at least in theory if not in experience.
|
||||
If you're here, you're probably familiar with each of these, at least in theory if not in experience.
|
||||
|
||||
# Let's Go
|
||||
## Let's Go
|
||||
|
||||
Enough setup, let's get to it.
|
||||
|
||||
## What do with with Hetzner
|
||||
### What do with with Hetzner
|
||||
|
||||
Head over to Hetzner DNS and retrieve an API token: https://dns.hetzner.com/settings/api-token
|
||||
|
||||
Save your token somewhere; you'll need it later.
|
||||
|
||||
## What to do with Nomad
|
||||
### What to do with Nomad
|
||||
|
||||
First of all, while I'm not going to get into the details of setting up host volumes, I will provide my cluster's client configuration for host volumes:
|
||||
|
||||
|
||||
**Create host volumes in `/opt/nomad/config/client.hcl`**:
|
||||
```
|
||||
#### Create host volumes in `/opt/nomad/config/client.hcl`
|
||||
```hcl
|
||||
client {
|
||||
enabled = true
|
||||
|
||||
|
|
@ -74,13 +74,14 @@ client {
|
|||
}
|
||||
```
|
||||
|
||||
> What does this config mean? We're making `/var/www/certbot` and `/etc/certbot` _from our nomad clients_ -- that is, the nodes that run nomad jobs -- available to be mounted inside of our nomad jobs. Doing so has implication that you should be aware of: **every node that your nginx job runs on will need to fetch new TLS certificates from Let's Encrypt**. If you're deploying your nginx job to dozens of nodes, or auto-scaling up and down frequently, this can result in you *hitting Let's Encrypt [API limits](https://letsencrypt.org/docs/rate-limits/)*. So tread lightly. Ideally, instead of "host volumes", you can use CSI volumes, where the `certbot` jobs claims a read/write CSI volume on startup, and `nginx` jobs make multi-node read claims to the same volume. That way, a single set of certs is shared across all nginx nodes.
|
||||
> What does this config do? We're making the directories `/var/www/certbot` and `/etc/certbot` from our nomad clients -- that is, the nodes that run nomad jobs -- available to be mounted inside our nomad jobs. Doing so has one implication that you should be aware of: **every node that your nginx job runs on will need to fetch new TLS certificates from Let's Encrypt the first time the job runs**. Therefore, if you're deploying your nginx job to dozens of nodes, or auto-scaling up and down frequently, this can result in hitting Let's Encrypt [API limits](https://letsencrypt.org/docs/rate-limits/). So tread lightly. Ideally, instead of "host volumes", you can use CSI volumes, where the `certbot` jobs claims a read/write CSI volume on startup, and `nginx` jobs make multi-node read claims to the same volume. That way, a single set of certs is shared across all nginx nodes.
|
||||
|
||||
After adding this client configuration, restart your client nodes.
|
||||
|
||||
**Create a combined Certbot / Ninx job**
|
||||
#### Create a combined Certbot / Ninx job
|
||||
|
||||
[Full Example job.nomad.hcl](/files/job.nomad.hcl)
|
||||
|
||||
```hcl
|
||||
variable "hetzner_dns_access_token" {
|
||||
type = string
|
||||
|
|
@ -328,20 +329,20 @@ EOF
|
|||
|
||||
While you'll obviously want to change all the `example.com` values, there are three other things you'll want to explicitly set:
|
||||
|
||||
You'll need to change the following line to be the name of one of your Nomad client nodes
|
||||
1. You'll need to change the following line to be the name of one of your Nomad client nodes
|
||||
`value = "cluster-0"`
|
||||
|
||||
When you're ready for production, you'll want to change `STAGING="--staging"` to `STAGING=""` in the `run.sh` script.
|
||||
2. When you're ready for production, you'll want to change `STAGING="--staging"` to `STAGING=""` in `run.sh`.
|
||||
|
||||
And if it's not obvious, you'll also want to change the `DOMAINS` variable in the `run.sh` script:
|
||||
`DOMAINS="example.com foo.com bar.com"` to your domains for which Hetzner controls the zone.
|
||||
3. And if it's not obvious, you'll also want to change the `DOMAINS` variable in `run.sh`:
|
||||
`DOMAINS="example.com foo.com bar.com"` to your domain(s).
|
||||
|
||||
|
||||
**Deploy the job**
|
||||
#### Deploy the job
|
||||
|
||||
`nomad run -var="hetzner_dns_access_token=<YOUR TOKEN HERE>" job.nomad.hcl`
|
||||
|
||||
# Bonus for Terraform users
|
||||
## Bonus for Terraform users
|
||||
|
||||
If you use the [Terraform Nomad provider](https://registry.terraform.io/providers/hashicorp/nomad/latest/docs), which I recommend you do, you can drop this into your terraform config to deploy the above job with Terraform:
|
||||
```hcl
|
||||
|
|
@ -360,12 +361,19 @@ resource "nomad_job" "nginx" {
|
|||
```
|
||||
|
||||
|
||||
# Conclusion And Caveats
|
||||
## Summmary
|
||||
|
||||
That's all here is to it. Certbot performs the DNS-01 challenge using Hetzner's DNS API, saves the issued certificates on a host volume shared with the nginx job, and nginx's configuration points at the same host volume to fetch domain certificates.
|
||||
What we've done here is created a single Nomad job that:
|
||||
|
||||
1. Has a `prestart` lifecycle task that ensures all certificates are available to the `nginx` job, using Nomad host volumes.
|
||||
2. Uses a Docker `cerbot` image with the Hetzner plugin, which performs a DNS-01 challenge using Hetzner's DNS API.
|
||||
3. Starts Nginx with Nomad host volumes mounted, containing the certificates retrieved using cerbot.
|
||||
|
||||
Note that if you run this job on multiple hosts, or auto-scale this job across multiple hosts, you should be aware of [Let's Encrypt rate limits](https://letsencrypt.org/docs/rate-limits/). If you end up requesting too many of certificates for the same domain, LE can block your domain for long periods of time, so be careful.
|
||||
|
||||
Also note that the above job does not automate the creation of records pointing at your nomad client running the nginx job. This is another reasin why I've pinned this job to a specific client node. I have existing DNS records pointing at that node.
|
||||
Also note that the provided Nomad job does not automate the creation of records pointing at your nomad client running the nginx job. It only creates DNS records for the DNS-01 challenge. This is why you'll notice that the job is to a specific client node. Doing so allows the same certificates to be reused until they expire.
|
||||
|
||||
The user will need to create their own DNS records pointing at the "pinned" node, for every domain listed in the `DOMAINS` variable.
|
||||
|
||||
Good luck, and feel free to reach out to me via email.
|
||||
|
||||
Good luck!
|
||||
|
|
|
|||
|
|
@ -66,7 +66,7 @@ paginate = 5
|
|||
|
||||
# whether to show a table of contents
|
||||
# can be overridden in a page's front-matter
|
||||
Toc = false # default
|
||||
Toc = true # default
|
||||
|
||||
# set title for the table of contents
|
||||
# can be overridden in a page's front-matter
|
||||
|
|
|
|||
Loading…
Reference in a new issue