Git Hooks pt3
Actual Deployment#
This whole “development pipeline” is a massive clunk. A collection of clunks, working together. Clunkware. I haven’t attached this deployment script to a git hook. I want a chance to look at the staging site before I push it to production. “Production” for me is when the files are updated on The VPS. Pushing a non-working site to production is, as the Irish would say, not the best. So I run the deployment script manually, which I will detail at the end of the post.
A word about rsync - Unix file permissions have been and always will be the destroyer of worlds. I find myself constantly using chmod and chown to put the hamster back on its wheel. In the staging script I used rsync to chown the files as they arrived on the Caddy Server root. I do this kind of thing a lot, but I don’t do it when it faces the Internet. For that, I use cron. The deployment goes a bit like this:
deploy.sh --> hugo build (now with working base URL!) --> rsync to me@vps.com:/home/me/www/example.com
/var/www on the VPS is owned by root:root. I do that because unlike staging, production is srs bzns. So I put the files to be published in the remote user’s home directory, where the points are made up and the permissions don’t matter.
THEN I let cron rsync the files as root via a script. It runs daily, as root via the /etc/cron.daily directory.
Script 2: deploy.sh#
- It takes the command line argument just like the previous script (deploy.sh example.com) and uses it to create variables for the different working trees:
set -euo pipefail
# You are supposed to "deploy.sh example.com"
# if you put anything other than 1 argument, exit
if [[ $# -ne 1 ]]; then
echo "Usage: $0 <repo-name>"
exit 1
fi
REPO_NAME="$1" # example.com
BUILD="$HOME/build/${REPO_NAME}" # ~/build/example.com/
DEPLOY="$HOME/deploy/${REPO_NAME}" # ~/deploy/example.com
URL="https://$REPO_NAME/"
- The script then looks at the repo name that you passed on the command line, and decides where to deploy. I did this so I can deply to multiple VPSes. I used a case statement rather than if/else.
case "${REPO_NAME}" in
example1.com | example2.com )
REMOTE="me1@vps1:/home/me/www/" # user@vps.com:/home/www/
;;
example3.com | example4.com )
REMOTE="me2@vps2:/home/me2/www/" # user@vps.com:/home/www/
;;
*)
echo "Unknown site ${REPO_NAME}"
esac
- Now check one last time that everything is good. This is probably not necessary.
# does the build exist? no? exit
if [[ ! -d "${BUILD}" ]]; then
echo "Build not found: ${BUILD}"
exit 1
fi
- Now use Hugo to build the production site, using the Hugo environment from before (in stage.sh), except this time it uses the domain name for the URL, which is essential for navigating the site.
# Hugo processes ~/build/example.com and outputs to ~/deploy/example.com
hugo -s "${BUILD}" --baseURL="${URL}" --destination="${DEPLOY}"
# sync preview/example.com to /var/www/example.com on the remote VPS
rsync -av "$DEPLOY/" "${REMOTE}${REPO_NAME}/"
I am not ready to make the script public either. Like I mentioned at the beginning, I wanted to be able to initiate build.sh from my laptop. So I created another shell script that sits in the root of my site’s writing environment. It’s important to understand that this runs on my laptop, and I use a different script for each site, which corresponds to the site’s domain name. I keep this consistent so that I can pass the domain name as a variable. The script runs on my laptop, it uses ssh to log into the staging server, run deploy.sh, and then exits.
#!/usr/bin/sh
ssh me@stagingserver '~/auto/deploy.sh example1.com'
Now, when I have finished writing, and I want to see the rendered page in the staging server, I just go to http://192.168.1.X/example1.com to make sure everything looks OK. Then, once I am satisfied that the page looks proper, I just type ./ship in WSL and the new page will publish sometime in the night. If I get antsy, I can log into the VPS, su to root, and run /etc/cron.daily/www-update to for the site to update.
I cannot stress how all of this work was done for me by Linux shell scripting. I think proper devs probably write all of these scripts in Python, but I have already taken on a lot of learning tasks as it is. That is The Unix Way: tiny tools that do only one thing, glued together with script. Like snapping together Lego.