title: Using webhooks to update a self-hosted Jekyll blog published: true description: Tutorial on installing webhook package on a Debian server, and configuring GitHub project to ping the server and deploy a Jekyll blog on every new commit. tags: webhook, github, jekyll, self-hosted


I host the blog of a community project on my personal server. It's a static website generated with Jekyll, and the contents are versionned in a git repository.

In this blog post, I explain how I made the blog automatically update on every new commit. It's the same idea as Github Pages, but for self-hosting.

TL;DR

  • create a script to clone the git repository, then build and deploy the website
  • publish a webhooks endpoint on the server
  • configure the GitHub repository to send a webhook on every new commit

Deploy script

The website is hosted on a Debian server, and the files are served from the /var/www/atlas.tecnologia.bo/ directory. To update it from command line, first install the Jekyll requirements, then create and launch the bash script:

$ /opt/deploy_atlas.tecnologia.bo.sh

that contains:

```bash

!/usr/bin/env bash

Clone git repository

rm -rf /tmp/atlas.tecnologia.bo git clone https://github.com/RipeAtlasBolivia/atlas.tecnologia.bo.git /tmp/atlas.tecnologia.bo

Generate blog

cd /tmp/atlas.tecnologia.bo bundle install bundle exec jekyll build --source /tmp/atlas.tecnologia.bo/ --destination /var/www/atlas.tecnologia.bo/

Clean

rm -rf /tmp/atlas.tecnologia.bo ```

Webhook daemon

Instead of manually launching the script, let a webhook daemon do the job every time it receives an HTTP request on an endpoint.

First install the webhook package:

bash $ sudo apt install webhook

Verify the service is running:

bash $ sudo service webhook status ● webhook.service - Small server for creating HTTP endpoints (hooks) Loaded: loaded (/etc/systemd/system/webhook.service; enabled; vendor preset: enabled) Active: active (running) since Mon 2019-08-12 08:25:24 UTC; 2 weeks 0 days ago Docs: https://github.com/adnanh/webhook/ Main PID: 419 (webhook) Tasks: 6 (limit: 4697) Memory: 4.8M CGroup: /system.slice/webhook.service └─419 /usr/bin/webhook -verbose -hooks /etc/webhook.conf

We configure the webhook daemon, creating the file /etc/webhook.conf with the following content (see the webhook documentation for more details):

json [ { "id": "atlas", "execute-command": "/opt/deploy_atlas.tecnologia.bo.sh", "command-working-directory": "/tmp/", "trigger-rule": { "match": { "type": "payload-hash-sha1", "secret": "xxxxxxxxx", "parameter": { "source": "header", "name": "X-Hub-Signature" } } } } ]

This creates a new hook called atlas, that will be triggered when a GET or POST request is received on http://localhost:9000/hooks/atlas. When triggered, it will check that a valid X-Hub-Signature HTTP header has been provided, and then launch the /opt/deploy_atlas.tecnologia.bo.sh script from the /tmp working directory.

Try it:

  • compute the X-Hub-Signature corresponding to the xxxxxxxxx secret

$ echo -n "" | openssl sha1 -hmac "xxxxxxxxx" (stdin)= d46d87941e6f285be78ff0f1c8ea32620577b9ef

  • request the endpoint:

``` $ curl -X POST -H "X-Hub-Signature: sha1=d46d87941e6f285be78ff0f1c8ea32620577b9ef" -v http://localhost:9000/hooks/atlas ...

POST /hooks/atlas HTTP/1.1 Host: localhost:9000 User-Agent: curl/7.64.0 Accept: / X-Hub-Signature: sha1=d46d87941e6f285be78ff0f1c8ea32620577b9ef

< HTTP/1.1 200 OK < Date: Mon, 26 Aug 2019 13:45:09 GMT < Content-Length: 0 < ```

  • check the logs:

$ grep webhook /var/log/syslog Aug 26 14:06:40 webhook[10552]: [webhook] 2019/08/26 14:06:40 Started POST /hooks/atlas Aug 26 14:06:40 webhook[10552]: [webhook] 2019/08/26 14:06:40 [d2ae30] incoming HTTP request from [::1]:58800 Aug 26 14:06:40 webhook[10552]: [webhook] 2019/08/26 14:06:40 [d2ae30] atlas got matched Aug 26 14:06:40 webhook[10552]: [webhook] 2019/08/26 14:06:40 [d2ae30] atlas hook triggered successfully Aug 26 14:06:40 webhook[10552]: [webhook] 2019/08/26 14:06:40 Completed 200 OK in 670.302µs Aug 26 14:06:40 webhook[10552]: [webhook] 2019/08/26 14:06:40 [d2ae30] executing /opt/deploy_atlas.tecnologia.bo.sh (/opt/deploy_atlas.tecnologia.bo.sh) with arguments ["/opt/deploy_atlas.tecnologia.bo.sh"] and environment [] using /tmp/ as cwd Aug 26 14:06:46 webhook[10552]: [webhook] 2019/08/26 14:06:46 [d2ae30] command output: Cloning into '/tmp/atlas.tecnologia.bo'... ... Aug 26 14:06:46 webhook[10552]: Bundle complete! 4 Gemfile dependencies, 24 gems now installed. Aug 26 14:06:46 webhook[10552]: Use `bundle info [gemname]` to see where a bundled gem is installed. Aug 26 14:06:46 webhook[10552]: Configuration file: /tmp/atlas.tecnologia.bo/_config.yml Aug 26 14:06:46 webhook[10552]: Source: /tmp/atlas.tecnologia.bo/ Aug 26 14:06:46 webhook[10552]: Destination: /var/www/atlas.tecnologia.bo/ Aug 26 14:06:46 webhook[10552]: Incremental build: disabled. Enable with --incremental Aug 26 14:06:46 webhook[10552]: Generating... Aug 26 14:06:46 webhook[10552]: Jekyll Feed: Generating feed for posts Aug 26 14:06:46 webhook[10552]: done in 0.45 seconds. Aug 26 14:06:46 webhook[10552]: Auto-regeneration: disabled. Use --watch to enable. Aug 26 14:06:46 webhook[10552]: [webhook] 2019/08/26 14:06:46 [d2ae30] finished handling atlas

Note: the 200 HTTP code only means the hook has been found for the atlas identifier and the X-Hub-Signature is valid. It says nothing about the deploy script success or failure.

In order to publish the endpoint from a Apache webserver, configure a reverse proxy in your Apache configuration:

ProxyPass /webhook/ http://localhost:9000/hooks/ ProxyPassReverse /webhook/ http://localhost:9000/hooks/

GitHub configuration

Finally, in order to trigger a webhook on every new push, go to the GitHub project settings, then "Webhooks", and create a new webhook with the following parameters:

  • Payload URL: https://mydomain/webhook/atlas
  • Content type: application/json
  • Secret: xxxxxxxxx
  • Which events would you like to trigger this webhook?: Just the push event.

Alternative configuration with GitLab

If your git repository is hosted on a GitLab instance, you must change the headers check in the /etc/webhook.conf file:

json [ { "id": "atlas", "execute-command": "/opt/deploy_atlas.tecnologia.bo.sh", "command-working-directory": "/tmp/", "trigger-rule": { "match": { "type": "value", "value": "xxxxxxxxx", "parameter": { "source": "header", "name": "X-Gitlab-Token" } } } } ]

Note: the X-Gitlab-Token header provided in the request will contain the value xxxxxxxxx, not its HMAC as it occurs with GitHub. So, to simulate the request, adapt the curl command to:

$ curl -X POST -H "X-Gitlab-Token: xxxxxxxxx" -v http://localhost:9000/hooks/atlas

Then, the GitLab project configuration is similar to GitHub: go to the project "Settings", "Integrations", and add a webhook with:

  • URL: https://mydomain/webhook/atlas
  • Secret Token: xxxxxxxxx
  • Trigger: [x] Push events

References

  • https://www.rasmuslarsson.se/post/hugo.html
  • https://github.com/adnanh/webhook/
  • https://jekyllrb.com/