all repos — website @ 664460b7823af4c7faa4cd8573585d92babc71bf

personal website hosted at prithu.xyz, built using hugo

complete git-remote-hooks post; styling changes
Prithu Goswami prithugoswami524@gmail.com
Fri, 15 Jan 2021 13:46:46 +0530
commit

664460b7823af4c7faa4cd8573585d92babc71bf

parent

9077f5ac84045a080462be78e598cb6c25ad2f35

3 files changed, 169 insertions(+), 54 deletions(-)

jump to
M content/posts/git-remote-hooks.mdcontent/posts/git-remote-hooks.md

@@ -1,15 +1,15 @@

--- title: "Using server-side git hooks to auto publish blog posts" -date: 2020-07-28T00:20:37+05:30 -draft: true +date: 2020-10-28T00:20:37+05:30 --- -Git hooks are great to automate your software development workflow. They can -also be used to implement CI/CD workflows. Many PaaS like netlify and heroku -trigger a build process of your app or website when you push to a remote -repository. In this post I want to go through how you could implement a similar -process on a VPS for a static blog using just a bash script with nginx serving -the files. +Git hooks are great to automate your software development workflow. They can be +used to implement CI/CD workflows. Many PaaS services like +[Netlify](https://netlify.com) and [Heroku](https://heroku.com) trigger a build +process of your app or website when you push to a remote repository. In this +post I want to go through how one could implement that automated process using +git hooks for a static blog using just a bash script with nginx serving the +files on a VPS. ## Setting up a remote git repository

@@ -17,21 +17,19 @@

Setting up a remote git repository on the VPS is as easy as doing: ``` -git init --bare website +$ git init --bare website ``` Usually it's a practice of creating a new user called 'git' on your server to -have access to your git repositories through ssh. I store my repositories in -the home directory of the git user on my server. Then make sure that you have -permissions to ssh as the 'git' user by including your ssh public in the -~/.ssh/authororized_keys file. So my git clone command would look something -like this: +have access to your git repositories through ssh. I keep my repositories in the +home directory of the 'git' user on my server. My git clone command would be +something like this: ``` -git clone git@git.prithu.xyz:website +$ git clone git@git.prithu.xyz:website ``` -There are other ways you can set up a remote git repository - using the ssh or +There are other ways you can set up a remote git repository - using the git or http protocols. I would suggest one reads the chapter [Git on the Server](https://git-scm.com/book/en/v2/Git-on-the-Server-The-Protocols) from the wonderful book on git - [Pro Git](https://git-scm.com/book/en/v2) to get to

@@ -41,10 +39,11 @@ If I already have a local repository, then I just push to this new one by

adding it as a remote using: ``` -git remote add myrepo git@git.prithu.xyz:website +$ git remote add origin git@git.prithu.xyz:website +$ git push --set-upstream origin master ``` -I can also initialize a new repo and add a remote to it manually. +...Or initialize a new one: ``` $ git init website

@@ -57,28 +56,36 @@ ```

## Understanding hooks -A hook is basically code that runs as a result of some event. In git we have -client-side hooks and server-side hooks. Client-side git hooks run on your -machine when perform actions on your local git repo. `pre-commit` is a -client-side hook that runs before you commit something, you can use this hook -to check for what is being commited and run tests for example to check if the -formating of the code conforms to a style guide. `post-merge` runs after a -successful merge takes place. `commit-msg` - This hook is invoked on `git -commit` and gets the name of the file that holds the commit message. Checks can -be performed on the commit message to see whether or not it conforms to a -standard format. If the hook exits with a non-zero cdoe, the commit is aborted. +A hook is basically code that runs as a result of some event. Here the hooks +are basically executable scripts/programs and reside in the `.git/hooks` +directory of a git repo. By default this directory has example scripts for a few +hooks (go take a look at them). You can read the man page +[githooks(5)](https://git-scm.com/docs/githooks) to get a list of all the +available git hooks. + +In git, we have client-side hooks and server-side hooks. Client-side git hooks +run on your machine when you perform actions on your local git repo. +`pre-commit` is a client-side hook that runs before you commit something, you +can use this hook to check for what is being committed and run tests. For +example, to check if the formating of the code conforms to a style guide, or +perform some sort of static analysis to find bugs and vulnerabilities. +`post-merge` runs after a successful merge takes place. The `commit-msg` hook +is invoked on `git commit` and gets the name of the file that holds the commit +message. Checks can be performed on the commit message to see whether or not +it conforms to a standard format. If the hook exits with a non-zero cdoe, the +commit is aborted. -We also have server-side hooks that live on the remote's bare repository. -`pre-recieve` hook runs before refs are updated on the remote. The script can -exit with a non-zero code and the push won't be accepted and the client will be -notified of the failure. `post-recieve` on the other hand runs when all the -refs are updated on the remote. This hook can be thought of as a hook that runs -when there is a 'push'. This hook gets information of what refs were updated - -if the master branch was updated then this information is passed on to the -script along with the last hash and the new updated hash. This information is -passed on to the script through the standard input. For example the -`post-recieve` script would have the following line would be passed to the -stdin. +We also have server-side hooks that are invoked on the remote repository. +`pre-recieve` hook runs before refs are updated on the remote, i.e before there +are any changes on the remote repository. The script can exit with a non-zero +code and the push won't be accepted and the client will be notified of the +failure. `post-recieve` on the other hand runs when all the refs are updated +on the remote. This hook can be thought of as a hook that runs when there is a +'push'. This hook gets information of what refs were updated - if the master +branch was updated then this information is passed on to the script along with +the last hash and the new updated hash. This information is passed on to the +script through the standard input. For example the `post-recieve` script would +have the following line passed to it through the standard input. ``` 689d4729b362e69a27600bb5bc26ca043c67f49f c60d357c48be63ff8ad8a6f94ab2f525332a9cd7 refs/heads/master

@@ -88,4 +95,112 @@ `689d472` is the old object hash, `c60d357` is the new hash value and

`refs/heads/master` is the ref being updated. So the `master` branch is now pointing to `c60d357` +A hook script can be written in any language as long as it can be an executable +file. +## Writing a hook script + +I will be using a simple shell script to checkout the latest commit on the +`website` repo, build the Hugo site and copy it to a location which will be +served by nginx. + +``` +#!/bin/bash + +set -o pipefail +read ref +echo "[`date`] $ref" >> /home/git/.build/logs/git +branch=$(echo $ref | cut -d ' ' -f 3) + +if [ "$branch" = "refs/heads/master" ] +then + echo "Building Hugo Site" + cd /home/git/.build/ + [ -d 'website' ] && rm -rf website + git clone /home/git/website > /dev/null 2>&1 && cd website + + logdate="$(date +%Y-%m-%d-%H%M)" + hugo | tee "/home/git/.build/logs/$logdate.log" || exit 1 + + rm -rf /home/git/.build/public + mv public /home/git/.build/ + + echo "Build complete" +fi +``` + +This script builds the Hugo site and then places the resultant static files in +the `/home/git/.build/public` directory. It's always a good idea to keep logs +and hence I log every build in the `/home/git/.build/logs/` directory. Now, all +I need is a webserver serving those files in the `public/` directory. + +In nginx for example, the following server block will do. + +``` +server { + listen 80 ; + listen [::]:80 ipv6only=on; + server_name prithu.xyz; + root /var/www/website; + charset utf-8; + + location / { + try_files $uri $uri/ =404; + } +} +``` + +I then create a symbolic link - `/var/www/website` -> +`/home/git/.build/public` + +``` +# ln -s /home/git/.build/public /var/www/website +``` + +I do this as the 'git' user does not have permissions to write to `/var/www/`; +hence a symbolic link is useful here. There might be other housekeeping chores +to be done like making sure that the 'git' user has permissions to do things +the script will do as the script is run as the 'git' user. + +Also, another thing to note is - anything the script writes to stdout will be +displayed to the client. The above script writes to the stdout a copy of Hugo's +output log. + +``` +$ git commit -m "some changes" +$ git push myrepo master +Enumerating objects: 5, done. +Counting objects: 100% (5/5), done. +Delta compression using up to 4 threads +Compressing objects: 100% (3/3), done. +Writing objects: 100% (3/3), 316 bytes | 316.00 KiB/s, done. +Total 3 (delta 2), reused 0 (delta 0), pack-reused 0 +remote: +remote: Building Hugo Site +remote: +remote: Start building sites … +remote: on. +remote: WARN 2021/01/15 11:34:13 found no layout file for "HTML" for kind "taxonomy": You should create a template file which matches Hugo Layouts Lookup Rules for this combinati +remote: on. +remote: WARN 2021/01/15 11:34:13 found no layout file for "HTML" for kind "term": You should create a template file which matches Hugo Layouts Lookup Rules for this combination. +remote: +remote: | EN +remote: -------------------+----- +remote: Pages | 21 +remote: Paginator pages | 0 +remote: Non-page files | 5 +remote: Static files | 19 +remote: Processed images | 0 +remote: Aliases | 0 +remote: Sitemaps | 1 +remote: Cleaned | 0 +remote: +remote: Total in 417 ms +remote: +remote: Build complete +remote: +To git.prithu.xyz:website + 4e7bec0..611ac8c master -> master +``` +And that's it! Every time I push to the master branch of this remote, my Hugo +site will be built and "deployed".
M layouts/posts/single.htmllayouts/posts/single.html

@@ -4,7 +4,7 @@ {{ partial "nav.html" }}

<div class="bcontainer"> <div class="bcontent"> <h1>{{ .Title }}</h1> - <p class="post-date">{{ .Date.Format "January 02, 2006" }}</p> + <p class="post-date">{{ .Date.Format "02 January, 2006" }}</p> <div class="post-labels"> {{ range .Params.tags }} <div class=post-label>{{ . }}</div>
M static/css/blog.cssstatic/css/blog.css

@@ -30,7 +30,7 @@ }

.bcontent{ /* background-color: lightgreen; */ - width: 55%; + width: 50%; } .bcontent h1{

@@ -73,12 +73,13 @@ margin: 0.2rem 0.2rem;

} .posts{ + font-family: Inter, sans-serif; list-style: none; margin-top: 1.5rem; } .posts > div:nth-child(odd){ - background-color: #E5E5E5; + background-color: #e6e6e6; } .post-item{

@@ -88,7 +89,7 @@ padding: 0.5rem 1rem;

} .post-item > a { - margin-right: 1rem; + margin-right: 4rem; overflow: hidden; }

@@ -102,8 +103,7 @@

.post-text{ /* font-family: Roboto, Sans-serif; */ font-family: Inter, sans-serif; - font-size: 18px; - color: rgba(0, 0, 0, 0.8); + font-size: 16px; }

@@ -122,7 +122,7 @@ margin-bottom: 1rem;

} .post-text a { - color:#B554E3; + border-bottom: 2px solid #B554E3aa; } /* .post-text a::after { */

@@ -137,7 +137,7 @@ /* } */

.post-text a:hover { color: #8a3ead; - text-decoration: underline; + text-decoration: none; } .post-text figure img{

@@ -145,7 +145,7 @@ margin: 0 auto;

} .post-text p{ - line-height: 1.4; + line-height: 1.5; margin: 1rem 0; }

@@ -196,18 +196,18 @@ background-color: rgba(3,122,255,0.8);

} .post-text > p > code{ - font-family: 'Roboto Mono'; + font-family: 'Roboto Mono', monospace; font-size: 14px; - background: rgba(0, 0, 0, 0.15); - border: 1px solid rgba(0,0,0,0.2); - padding: 0 0.4rem; + background: rgba(0,0,0,0.08); + border: 1px solid rgba(0,0,0,0.1); + padding: 0 0.3rem; border-radius: 0.2rem; } .post-text > p > a > code{ font-family: 'Roboto Mono'; font-size: 14px; - background: rgba(0, 0, 0, 0.15); + background: rgba(0, 0, 0, 0.08); border: 1px solid rgba(0,0,0,0.1); padding: 0 0.4rem; border-radius: 0.2rem;

@@ -216,7 +216,7 @@

.post-text blockquote { margin: 2rem 0rem 2rem 1rem; padding-left: 1rem; - border-left: 4px solid rgba(0,0,0,0.4); + border-left: 4px solid rgba(0,0,0,0.2); font-size: 16px; color: rgba(0,0,0,0.7); text-transform: italic;