Dipping a toe into Github deployments

anthony — Thursday, 13 June 2019

Automated deployments can seem like an advanced step that's only really required when you're a team of people working on a big-deal project, but that's not really true.  It's often the solo coder that can benefit most from saving time fretting with deployments, especially if you're working on a low-stakes personal project and are treating the server as a development sandbox as you go along.

I'm going to assume your current workflow is:

  1. Make a code change, commit and push to your github project
  2. SSH into your server, change to your project directory, `git pull` the new changes.
  3. Restart your app (something like `sudo systemctl restart myapp`)
Webhooks to the Rescue

Webhooks are essentially little mailboxes that various apps can have to let them receive messages sent by other services.  In our case, when we commit a change to our Github project, we're going to ask Github to automatically send a message to our webhook, which will then trigger all the boring work for us, automatically.

I'm going to assume you already have a server, and on that server you're created a ssh key. (You'll only need the ssh key if your project is private)

So the next step is to create our webhook— frustratingly the project name is also called webhook, which makes Googling around for it a bit of a pain, but fear not, just do the following on your server:

sudo apt-get update
sudo apt-get install webhook

The installation will create a system service `webhook.service` which will run our little webhook server— if we look in that file `sudo nano /lib/systemd/system/webhook.server` we'll see it's rough setup:

[Unit]
Description=Small server for creating HTTP endpoints (hooks)
Documentation=https://github.com/adnanh/webhook/
ConditionPathExists=/etc/webhook.conf
[Service]
ExecStart=/usr/bin/webhook -nopanic -hooks /etc/webhook.conf
[Install]
WantedBy=multi-user.target

You can see it want's to load `/etc/webhook.conf`by default, we're going to change the file just a little to make things a little more streamlined:

[Unit]
Description=Small server for creating HTTP endpoints (hooks)
Documentation=https://github.com/adnanh/webhook/
ConditionPathExists=/etc/webhook/hooks.json
[Service]
ExecStart=/usr/bin/webhook -nopanic -hooks /etc/webhook/hooks.json -hotreload
[Install]
WantedBy=multi-user.target

Essentially we've just told it to look at an alternate location (I find it tidier that way) and to reload when changes are made to the configuration automatically.  We also adjusted the `ConditionPathExists` so the service neatly errors out if we mess up the next step, creating the `hooks.json file`:

sudo mkdir /etc/webhook
sudo nano /etc/webhook/hooks.json

Within there we're going to set up the actions that happen when Github tells our webhook that a commit has taken place:



[
    {
        "id": "webhook",
        "execute-command": "/home/you/webhooks/myapp.sh",
        "command-working-directory": "/home/you/webhooks",
        "response-message": "Deploying new application from Github",
        "trigger-rule": {
            "match": {
                "type": "payload-hash-sha1",
                "secret": "the-password-to-protect-the-webhook",
                "parameter": {
                    "source": "header",
                    "name": "X-Hub-Signature"
                }
            }
        }
    }
]

The main elements here are what command to execute, you'll want to point that at the script that we're going to create to handle the deployment task, and importantly you want to set a secret.  Without it, anyone can send a message to your webhook, and we want to reject any requests that don't have the secret knock.

Next up is creating the `/home/you/webhooks/myapp.sh` file, so `nano /home/you/webhooks/myapp.sh` and put something like the following in:

#! /bin/bash
cd /home/you/yourproject
sudo -u you git pull
systemctl restart yourproject

The script executes as the system, we want to generally run the commands as your lower-level user— so we'll use sudo to become your user (rename `you` to your username) to do the pull request, then we'll restart the service associated with your application.  We'll want to make the script executable, so `chmod +x /home/you/webhooks/myapp.sh` will do that.  Let's fire up the webhook so it's ready for the next step:

sudo systemctl daemon-reload
sudo systemctl start webhook

Finally we'll close the loop by telling Github about our webhook, navigate to the project on the Github site and go into webhooks within the project settings.  The payload URL is simply going to be your server, on port 9000, e.g. `http://myserver.com:9000/hooks/webhook` (an IP address is fine if you don't have a domain hooked up to it yet). The `webhook` part of the URL is set by the `id` you specified in `hooks.json`.

We want to set `Content Type` as `application/json` and set our `Secret` to match the secret we set earlier.  We only really want to notify the webhook on `push` events and we, of course, want it active. If this is a private project we also need to go into `Deploy Keys` and copy and paste our ssh public key from earlier `cat ~/.ssh/id_rsa.pub` to let our server pull from the project.

Now when you commit and push a change to the project, Github will alert your webhook-- which in turn will run the script we created to pull those changes and reload the project.