Webhooks as a (Systemd) Service
The "docs" microsite for xoxo.zone is a static page built with
Eleventy. I explicitly didn't want to overcomplicate the site's setup with a
cloud build process triggered by commit actions: the static site is compiled
locally and committed alongside the content changes. A cron on the server runs
git pull
on the repo every 5 minutes. The web server can directly serve the
site without any additional build.
The site is updated a couple times a month on average. Of the 8,640 average monthly cron git pulls, 8,638 will do nothing. The net impact of this is probably negligible, but it did annoy me. Besides, Codeberg (my forge of choice for xoxo) gets the occasional DDoS and I'm sure is getting hammered by "AI" scrapers that never sleep. Why send them more traffic than necessary?
I wanted to update the site when I pushed to the repo, without a complex configuration that would be difficult for someone else to pick up. Maybe I could write a lightweight web server that listened for a request and perform an action?
Stop giving things generic names
The obvious choice for triggering an action on push is a Webhook, which Codeberg supports natively. Expose an endpoint, verify incoming requests, run a command.
Doing some preliminary searches, I stumbled across the extremely generically-named webhook, a Go web server for triggering commands based on webhook requests. It's apt-installable on Ubuntu, has a simple configuration file syntax in JSON or YAML, and doesn't mind being proxied behind nginx. From a maintenance perspective, that's better than rolling my own server.
Webhook configuration
After apt install webhook
, I customised some of the launch parameters with
systemctl edit webhook
1:
[Unit]
ConditionPathExists=
ConditionPathExists=/etc/webhook.yaml
[Service]
ExecStart=
ExecStart=/usr/bin/webhook -nopanic -hooks /etc/webhook.yaml -ip 127.0.0.1 -port 9899 -urlprefix my-webhook-prefix
User=www-data
Group=www-data
I configured my hook in /etc/webhook.yaml
. webhook
has an awkward syntax for
passing arguments to commands, so I made a small bin script to wrap cd
and
git pull ...
. webhook
also includes "matchers" to further customise the hook
based on incoming parameters. This config performs request signature
verification based on a shared secret, and filters for push events.
- id: deploy-docs
execute-command: /usr/local/bin/xoxo-docs-pull
response-message: ok
trigger-rule:
and:
- match:
type: payload-hmac-sha256
secret: my_secret
parameter:
source: header
name: X-Forgejo-Signature
- match:
type: value
value: push
parameter:
source: header
name: X-Forgejo-Event
Then I proxied the requests through Nginx:
upstream webhooks {
server 127.0.0.1:9899 fail_timeout=5;
}
server {
# the rest of the server config...
location ~ ^/my-webhook-prefix/ {
proxy_pass http://webhooks;
}
}
That's enough to get a working webhook. No more cron needed!
The future
Mastodon itself supports webhooks, and I'd love to improve our admin hooks in
the future. Today we get a Discord message when a report is created, but it's
not very readable and there's no ability to update the initial message when the
report is actioned. webhook
feels like a good starting place to improve that
experience.