Files
ettore.dreucci.it/content/blog/deploy-hugo-git.md
2025-10-10 00:37:31 +02:00

114 lines
5.3 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
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 webhooks 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, lets 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. Youre done! Write a new post, push it and see the magic ;)