114 lines
5.3 KiB
Markdown
114 lines
5.3 KiB
Markdown
---
|
||
title: "Deploy a Hugo Website with GIT"
|
||
tags: ["hugo", "gogs", "git", "deploy", "webhooks", "hooks"]
|
||
categories: ["recipe", "sysadmin"]
|
||
description: "How to deploy a hugo-powered static website to a webserver with Gogs"
|
||
date: 2019-05-27T20:34:30+02:00
|
||
author: "Ettore Dreucci"
|
||
draft: false
|
||
---
|
||
|
||
## How to deploy a hugo-powered static website to a webserver with GIT
|
||
|
||
[This](https://ettore.dreucci.it) website is powered by [Hugo](https://gohugo.io/), an open-source static website generator written in [Go](https://golang.org/). I can write pages and posts in [Markdown](https://daringfireball.net/projects/markdown/) and then generate some static .html pages to upload to a webserver.
|
||
|
||
For this project, as I usually do, I push every change to a self-hosted [GIT](https://git-scm.com/) server with [Gogs](https://gogs.io/) (another open-source, golang written GIT server), so why not use webhooks to automate the deployment?
|
||
|
||
Gogs, as GitHub, GitLab and other well-known services provides webhooks to notify other services of changes in a repository: they are basically HTTP callbacks triggered by some user-defined events such as a push or a pull request. Each time you, for instance, push to a repo, Gogs will send a **HTTP POST** payload to the webhook’s configured URL: the payload will contain the relevant event information.
|
||
|
||
To protect from unauthorized requests you could tell Gogs to send, in the header of the POST request, a `X-Gogs-Signature` that contains the **HMAC hex digest of the payload** generated using the `sha256` hash function and a **secret** as the HMAC key.
|
||
|
||
My event-chain is the following: I push the new content to the repo, Gogs trigger a webook that send a HTTP POST payload to a .php page in the webserver. The php-page pulls the repo to the server, generates the static website with `hugo` and copies the newly generated `public` directory to the document root of the webserver.
|
||
|
||
#### So, let’s see the practical part:
|
||
|
||
1. Add in the Gogs repository configuration an ssh *Deploy Key*, to pull the repo in the webserver. The *deploy key* will have read-only access.
|
||
You can generate the ssh key with `ssh-keygen` but keep it password-less to make the pull completely automated.
|
||
|
||
2. Clone the repo in the document root with
|
||
````
|
||
git clone user@git.server:repo /var/www/
|
||
````
|
||
|
||
3. Insert in your `.htaccess` file the secret like that:
|
||
````
|
||
SetEnv GOGS_DEPLOY_SECRET y0uRs3cRetC0d3
|
||
````
|
||
|
||
4. Create a `deploy.php` page that will pull the repo, generate the website and deploy it when called.
|
||
You can use the following script:
|
||
````
|
||
<?php
|
||
/**
|
||
* Automated deploy from Gogs
|
||
*
|
||
* Template from ServerPilot (https://serverpilot.io/community/articles/how-to-automatically-deploy-a-git-repo-from-bitbucket.html)
|
||
* Hash validation from liogate (https://github.com/gogs/gogs/issues/4233#issue-211797295)
|
||
*/
|
||
|
||
// Variables
|
||
$secret = getenv('GOGS_DEPLOY_SECRET');
|
||
$repo_dir = '/path/to/repo/';
|
||
$web_root_dir = '/var/www/';
|
||
$rendered_dir = '/public';
|
||
$hugo_path = '/usr/local/bin/hugo';
|
||
|
||
// Validate hook secret
|
||
if ($secret !== NULL) {
|
||
// Get signature
|
||
$gogs_signature = $_SERVER['HTTP_X_GOGS_SIGNATURE'];
|
||
|
||
// Make sure signature is provided
|
||
if (!isset($gogs_signature)) {
|
||
file_put_contents('deploy.log', date('m/d/Y h:i:s a') . ' Error: HTTP header "X-Gogs-Signature" is missing.' . "\n", FILE_APPEND);
|
||
die('HTTP header "X-Gogs-Signature" is missing.');
|
||
} elseif (!extension_loaded('hash')) {
|
||
file_put_contents('deploy.log', date('m/d/Y h:i:s a') . ' Error: Missing "hash" extension to check the secret code validity.' . "\n", FILE_APPEND);
|
||
die('Missing "hash" extension to check the secret code validity.');
|
||
}
|
||
|
||
// Get payload
|
||
$payload = file_get_contents('php://input');
|
||
|
||
// Calculate hash based on payload and the secret
|
||
$payload_hash = hash_hmac('sha256', $payload, $secret, false);
|
||
|
||
// Check if hashes are equivalent
|
||
if (!hash_equals($gogs_signature, $payload_hash)) {
|
||
// Kill the script or do something else here.
|
||
file_put_contents('deploy.log', date('m/d/Y h:i:s a') . ' Error: Bad Secret' . "\n", FILE_APPEND);
|
||
die('Bad secret');
|
||
}
|
||
|
||
};
|
||
|
||
// Parse data from Gogs hook payload
|
||
$data = json_decode($_POST['payload']);
|
||
|
||
$commit_message;
|
||
if (empty($data->commits)){
|
||
// When merging and pushing to Gogs, the commits array will be empty.
|
||
// In this case there is no way to know what branch was pushed to, so we will do an update.
|
||
$commit_message .= 'true';
|
||
} else {
|
||
foreach ($data->commits as $commit) {
|
||
$commit_message .= $commit->message;
|
||
}
|
||
}
|
||
|
||
if (!empty($commit_message)) {
|
||
// Do a git pull, run Hugo, and copy files to public directory
|
||
exec('cd ' . $repo_dir . ' && git pull');
|
||
exec('cd ' . $repo_dir . ' && ' . $hugo_path);
|
||
exec('cd ' . $repo_dir . ' && cp -r ' . $repo_dir . $rendered_dir . '/. ' . $web_root_dir);
|
||
|
||
// Log the deployment
|
||
file_put_contents('deploy.log', date('m/d/Y h:i:s a') . " Deployed branch: " . $branch . " Commit: " . $commit_message . "\n", FILE_APPEND);
|
||
}
|
||
````
|
||
|
||
5. In the Gogs repository webpage, activate a webhook, specifying the `deploy.php` URL, the content type (`application/json`) and a secret. Then select the event(s) that will trigger the webhook.
|
||
|
||
6. You’re done! Write a new post, push it and see the magic ;)
|
||
|