Intro

If you’ve been keeping up with my earlier blog entries, such as 01-caddy-in-docker and 06-caddy-for-quartz, you now have on your server a Caddy Docker instance successfully serving a static site powered by Quartz.

Given that I edit my notes using multiple devices, Obsidian and Obsidian Git plugin, I’ve been searching for a solution for automatically deploying my Quartz changes directly to my server.

One straightforward approach is to update the Git repository directly, which is served by Caddy, using the git pull command, followed by rebuilding the static files. However, this method requires continuous access to the server and involves a relatively manual process.

In this guide, I will demonstrate how to update your server repository by leveraging a combination of Github Webhooks and Caddy configuration.


Create Update Script

Create a shell script named update-quartz with the following content:

#!/bin/sh
# /path/to/script/update-quartz
 
(cd /path/to/quartz && git pull origin main && npx quartz build)

This script will update the git repository then rebuild the Quartz static files from your notes.


Update Caddy Docker Image

Caddy Exec Plugin

caddy-exec is a Caddy plugin designed for executing background shell commands. Its utility becomes evident when employing it to directly run our earlier update script within the Caddy environment.

To add the plugin to your Caddy docker instance, you can use the Dockerfile from 04-install-caddy-plugins:

FROM caddy:builder AS builder
RUN xcaddy build \
	--with github.com/abiosoft/caddy-exec
 
FROM caddy:latest
COPY --from=builder /usr/bin/caddy /usr/bin/caddy

Script Dependencies

The update-quartz also have dependencies that needs to be installed:

FROM caddy:builder AS builder
RUN xcaddy build \
       --with github.com/abiosoft/caddy-exec
 
FROM caddy:latest
COPY --from=builder /usr/bin/caddy /usr/bin/caddy
RUN apk add --update nodejs npm git

Environment

To be able to execute the update-quartz command, Caddy need the script to be in the PATH variable. Edit the Dockerfile as follow:

FROM caddy:builder AS builder
RUN xcaddy build \
       --with github.com/abiosoft/caddy-exec
       
FROM caddy:latest
COPY --from=builder /usr/bin/caddy /usr/bin/caddy
RUN apk add --update nodejs npm git
ENV PATH="${PATH}:/path/to/script"

And add a volume mapping the update-quartz script on the docker-cpopose.yml of Caddy:

version: "3.9"
 
services:
  caddy:
    image: caddy:latest
    container_name: caddy
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
      - "443:443/udp"
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile
      - ./data:/data
      - ./config:/config
      - /path/to/script:/path/to/script
 
networks:
  default:
    external:
      name: caddy

Create Caddy /update Endpoint

Next, let’s establish an /update endpoint, which will serve as the trigger point for initiating the update of the server’s Git repository through Caddy.

From the Caddy config of 06-caddy-for-quartz, add the configuration below:

domain.name {

	route /update {
		exec update-quartz
	}

	root * /path/to/quartz/public
	try_files {path} {path.html}
	file_server

	handle_errors {
		@404-410 expression `{err.status_code} in [404, 410]`
		handle @404-410 {
			root * /path/to/quartz/public
			rewrite * {err.status_code}.html
			file_server
		}
	}
}

With this configuration, the update can be triggered manually by calling the /update endpoint on our server.

This endpoint is currently accessible by all the internet. To create a basicauth authentication protection, generate a password hash using the command:

docker exec -it caddy caddy hash-password

For the password 1234, the output will look like:

Enter password:  
Confirm password:  
$2a$14$S9xXKUJUueIzZFFPFhrqQOkCJs.12XeyHSybjpXNBLCLQWli7wIva

This command will output a string in the bcrypt format by default. You can look at the Caddy hash-password documentation for more information.

Complete the Caddy config file with basicauth directive, specifying the hash-password output and a user that will be used to authenticate:

domain.name {

	route /update {
		basicauth {
			user $2a$14$S57sqa8RAHPBTRYvy.GqYOQOoPBeip.zZ9W.yvmQKck61thG72bKy
		}

		exec update-quartz
	}

	root * /path/to/quartz/public
	try_files {path} {path.html}
	file_server

	handle_errors {
		@404-410 expression `{err.status_code} in [404, 410]`
		handle @404-410 {
			root * /path/to/quartz/public
			rewrite * {err.status_code}.html
			file_server
		}
	}
}

With this configuration, the endpoint /update will be protected with credentials user/1234.


Setup Github Webhook

Go to Github in Settings => Webhooks => Add webhook. For the Payload URL parameter, set the URL https://user:1234@domain.name/update.

By default, webhooks will be triggered on push event. This implies that when you push your modifications to Quartz on Github, the webhook will autonomously activate the /update endpoint on your Caddy server. Consequently, it will update and rebuild the Quartz static files, which are also served by your Caddy instance 🚀.


Improve Security

To safeguard your Caddy endpoint /update from potential brute force attacks, consider implementing custom Fail2Ban filters as outlined in the blog post 05-fail2ban-for-caddy.


Ressources