Using webhooks to update a self-hosted Jekyll blog

severo

Sylvain Lesage

Posted on August 26, 2019

Using webhooks to update a self-hosted Jekyll blog

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:

#!/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:

$ sudo apt install webhook

Verify the service is running:

$ 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):

[
  {
    "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:

[
  {
    "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

๐Ÿ’– ๐Ÿ’ช ๐Ÿ™… ๐Ÿšฉ
severo
Sylvain Lesage

Posted on August 26, 2019

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related

ยฉ TheLazy.dev

About