---
draft: false
title: "Automating Caddy on my DigitalOcean Droplet"
date: "2023-01-05"
author: "Nick Dumas"
authorTwitter: "" #do not include @
cover: ""
tags: ["webdev", "devops"]
keywords: ["", ""]
summary: "Automation ambitions fall flat"
showFullContent: false
---

## Defining units of work
I've got a few different websites that I want to run: this blog, my portfolio, and my about page which acts as a hub for my other sites, my Bandcamp, and whatever else I end up wanting to show off.

To keep things maintainable and reproducible, I decided to stop attempting to create a monolithic all-in-one configuration file. This made it way harder to keep changes atomic; multiple iterations on my blog started impacting my prank websites, and it became harder and harder to return my sites to a working state.

## Proof of concept

The first test case was my blog because I knew the Hugo build was fine, I just needed to get Caddy serving again.

A static site is pretty straightforward with Caddy:

```
blog.ndumas.com {
    encode gzip
    fileserver
    root * /var/www/blog.ndumas.com
}
```

And telling Caddy to load it:

```bash
curl "http://localhost:2019/load" \
	-H "Content-Type: text/caddyfile" \
	--data-binary @blog.ndumas.com.caddy
```

This all works perfectly, Caddy's more than happy to load this but it does warn that the file hasn't be formatted with `caddy fmt`:

```
[{"file":"Caddyfile","line":2,"message":"input is not formatted with 'caddy fmt'"}]
```

## The loop

Here's where things went sideways. Now that I have two unit files, I'm ready to work on the tooling that will dynamically load my config. For now, I'm just chunking it all out in `bash`. I've got no particular fondness for `bash`, but it's always a bit of a matter of pride to see whether or not I *can*.

```bash
# load-caddyfile
#! /bin/bash

function loadConf() {
    curl localhost:2019/load \
        -X POST \
        -H "Content-Type: text/caddyfile" \
        --data-binary @"$1"
}

loadConf "$1"
```

```bash
# load-caddyfiles
#! /bin/bash

source load-caddyfile

# sudo caddy stop
# sudo caddy start
for f in "$1/*.caddy"; do
    echo -e "Loading $(basename $f)"
    loadConf "$f"
    echo
done
```

After implementing the loop my barelylegaltrout.biz site started throwing a 525 while blog.ndumas.com continued working perfectly. This was a real head scratcher, and I had to let the problem sit for a day before I came back to it.

After some boring troubleshooting legwork, I realized I misunderstood how the `/load` endpoint works. This endpoint completely replaces the current config with the provided payload. In order to do partial updates, I'd need to use the `PATCH` calls, and look who's back?

## can't escape JSON

The `PATCH` API *does* let you do partial updates, but it requires your payloads be JSON which does make sense. Because my current set of requirements explicitly excludes any JSON ( for now ), I'm going to have to ditch my dreams of modular code.



Not all my code has gone to waste, though. Now that I know `POST`ing to to `/load` overwrites the whole configuration, I don't need to worry about stopping/restarting the caddy process to get a clean slate. `load-caddyfile` will let me keep iterating as I make changes.

## Proxies

In addition to the static sites I'm running a few applications to make life a little easier. I'll showcase my Gitea/Gitlab and Asciinema configs. At the moment, my setup for these are really janky, I've got a `tmux` session on my droplet where I've manually invoked `docker-compse up`. I'll leave cleaning that up and making systemd units or something proper out of them for a future project.

Reverse proxying with Caddy is blessedly simple:
```
cast.ndumas.com {
    encode gzip
    reverse_proxy localhost:10083
}
```

```
code.ndumas.com {
    encode gzip
    reverse_proxy localhost:3069
}
```

With that, my gitea/gitlab is up and running along with my Asciinema instance is as well:

[![asciicast](https://cast.ndumas.com/a/28.svg)](https://cast.ndumas.com/a/28)

## Back to Square One
After finally making an honest attempt to learn how to work with Caddy 2 and its configurations and admin API, I want to take a swing at making a systemd unit file for Caddy to make this a proper setup.

## Finally

Here's what's currently up and running:
- [My blog](blog.ndumas.com)
- [Asciinema](blog.ndumas.com)
- [Gitea](blog.ndumas.com)

I've had loads of toy projects over the years ( stay tuned for butts.ndumas.com ) which may come back, but for now I'm hoping these are going to help me focus on creative, stimulating projects in the future.

The punchline is that I still haven't really automated Caddy; good thing you can count on `tmux`
[![asciicast](https://cast.ndumas.com/a/aWYCFj69CjOg94kgjbGg4n2Uk.svg)](https://cast.ndumas.com/a/aWYCFj69CjOg94kgjbGg4n2Uk)

The final code can be found [here](https://code.ndumas.com/ndumas/caddyfile). Nothing fancy, but troublesome enough that it's worth remembering the problems I had.
