Webhook to auto-deploy on git push to Github
Tags: git, linux, bash, productivity
Published: 2020-05-25 18:13:21

## What is a webhook?

A [webhook](https://en.wikipedia.org/wiki/Webhook) is an endpoint on your server which allows you to execute a particular task. Webhooks are usually triggered by some event. A good use-case for a webhook is running tests on a dedicated test server or deploying your latest `master` branch to staging/production.

[Github](https://developer.github.com/webhooks/) / [Gitlab](https://docs.gitlab.com/ee/user/project/integrations/webhooks.html) / Bitbucket allow you to specify a webhook URL in your repository settings. Github triggers the webhook which sends the event data on every push.

## [Webhook](https://github.com/adnanh/webhook) server

[Webhook](https://github.com/adnanh/webhook) is a very useful golang project which runs any script you specify when a particular endpoint is hit. 

Download and extract the binary for your operating system from the [releases page](https://github.com/adnanh/webhook/releases). For Linux, it is [here](https://github.com/adnanh/webhook/releases/download/2.7.0/webhook-linux-amd64.tar.gz).

The program takes as config a **`hooks.json`** file:

```json
[
  {
    "id": "hello-world",
    "execute-command": "/home/user/scripts/hello.sh",
    "command-working-directory": "/home/user/webhook"
  }
]
```
Replace `user` with the username of your linux user.

The `hello.sh` script.
```bash
#!/bin/bash
echo 'Hello!'
```
Make the script executable by running **`chmod +x hello.sh`**

Start webhook server as **`webhook -hooks hooks.json -hotreload -logfile webhooks.log`**. The server will run on port 9000 by default. You can check if everything is working by running **`curl http://localhost:9000/hooks/hello-world`**. This will print "Hello!" in the log file.

### Deploy script

For the purpose of this post, I'll assume the script is called **`deploy`** and is at location `/home/user/scripts/deploy`. This script will vary depending on your tech stack and the complexity of your CI process.

A simple example `deploy` script:
```bash
#!/bin/bash
# If you have a build server which creates binary/jar/artifact
wget 'ARTIFACT_URL'
# Else, git pull and build on the server itself
# Assuming this script stops old instance of your code and starts a new instance with latest artifact 
restart-service.sh
```

### Configuration to run deploy script
```json
[
  {
    "id": "deploy-from-git",
    "execute-command": "/home/user/scripts/deploy",
    "command-working-directory": "/home/user/scripts",
    "trigger-rule":
    {
      "and":
      [
        {
          "match":
          {
            "type": "payload-hash-sha1",
            "secret": "MyTotallySecretString",
            "parameter":
            {
              "source": "header",
              "name": "X-Hub-Signature"
            }
          }
        },
        {
          "match":
          {
            "type": "value",
            "value": "refs/heads/master",
            "parameter":
            {
              "source": "payload",
              "name": "ref"
            }
          }
        }
      ]
    }
  }
]
```
The **trigger-rule** in config above will ensure that the script is only triggered when header from Github request contains **"X-Hub-Signature"** with a secret string and the push has occured in master branch.

Make sure that the secret string ("secret" : "MyTotallySecretString") is randomly generated. This secret will need to be entered in Github settings as well.

For Gitlab and Bitbucket, example hook config can be found on repo page [here](https://github.com/adnanh/webhook/blob/master/docs/Hook-Examples.md)

### Expose your webhook server safely to the internet

There are 2 ways of exposing the webhook server to github:
- Proxy using Nginx
- Via a tunnel e.g by downloading [ngrok](https://ngrok.com/product) and then running `ngrok http 9000`

#### Nginx configuration
Preferably use HTTPS for your domain with Nginx. A good tutorial [here](https://www.digitalocean.com/community/tutorials/how-to-secure-nginx-with-let-s-encrypt-on-ubuntu-18-04).

Example Nginx config (HTTPS):
```
upstream webhook {
    server localhost:9000;
}

server {
	listen 443 ssl http2;
        server_name YOUR.DOMAIN.COM;
	ssl_certificate YOUR_CERT_CHAIN; # e.g /etc/letsencrypt/live/DOMAIN/fullchain.pem;
	ssl_certificate_key YOUR_CERT_KEY; # e.g /etc/letsencrypt/live/DOMAIN/privkey.pem;
	include /etc/nginx/options-ssl-nginx.conf;
	ssl_dhparam /etc/nginx/ssl-dhparams.pem;
        location ~ ^/hooks/(.+)$ {
		proxy_pass http://webhook;
	}
}
```

### Add your webhook URL to Github

Go to the settings page of your Github repo then click on Webhook. Enter the URL of your webhook server. If using Nginx, it should be something like `https://YOUR.DOMAIN.COM/hooks/deploy-from-git`. Make sure you select content type as `application/json` and secret to the secret you generated earlier.

### Bonus: Create a systemd user service for webhook (Linux)

Create a systemd unit file with path **/home/user/.config/systemd/user/webhook.service** . This service does not require sudo/root permissions and can be run by the unprivileged user.

```
[Unit]
AssertPathExists=/home/user/scripts

[Service]
WorkingDirectory=/home/user/scripts
ExecStart=/home/user/scripts/webhook -hooks hooks.json -hotreload -logfile webhooks.log
Restart=always
PrivateTmp=true
NoNewPrivileges=true

[Install]
WantedBy=default.target
```

Do `systemctl --user daemon-reload` and `systemctl --user start webhook.service`. You can `systemctl --user enable webhook.service` to ensure that the service always runs when your machine is booted.