Deloying to the server
Jan 14 2017This is part 3 of my – sometimes rambling – review of building the latest iteration of pddds. In part 1 I reviewed the tech, tools and features I used, in part 2 I covered the workflow I used to develop the site.
The last piece of the puzzle when building the website (other than actually
making it) was to decide how to upload it. I could have just copied the
files generated by jekyll onto the server using scp
, but that’s fiddly and
repetitive so why not automate it?
The logical way to do this was via git; I can push changes from git and use a server-side hook to rebuild the site.
Setting up the server
First you need a git repository on the server to push to. Although you’ll need to check out the repo to build it, you only need a bare repository, i.e. one without a checked out working copy.
Connect to the server using ssh and do:
git init --bare ~/path/to/repo
That will create create the folder ~/path/to/repo.git
which will contain the
git info. Then, from the local machine you can add it as a remote as deploy
:
git remote add deploy peter@server-address.com:~/path/to/repo.git
and push the local master branch to the server
git push deploy master
With the bare repo setup, we can send the latest version to the server but we still need to add a hook to run jekyll, building the final website.
Deploying
Git has a number of hooks you can register, they are scripts that run on specific events. To build the site after receiving pushed changes, we can use the post-receive hook.
The post-receive hook should be created on the server, in the repo.git/hooks
folder.
A simple post-receive hook, which just print to the terminal when changes are pushed, looks like:
#!/bin/bash
while read oldVal newVal refName; do
echo "recieved branch $refName"
done
To build the site, we need the hook to checkout a working copy of the repository and run jekyll, outputting to the public html folder. I found this tricky to get right, but ended up with something similar to the following.
#!/bin/bash
set -e
TMP_CLONE="$TMPDIR/website-git-clone"
PUBLIC_WWW="/home/public"
build(){
local branch="$1"
branch="${branch#refs/}"
branch="${branch#heads/}"
local out="$PUBLIC_WWW"
if [[ "$branch" != "master" ]]; then
return
fi
local clone="$TMP_CLONE"
echo checking out
mkdir -p "$clone"
git --work-tree="$clone" checkout -f "$branch"
git --work-tree="$clone" reset --hard HEAD
echo building
JEKYLL_ENV=production jekyll build -s "$clone" -d "$out"
}
while read oldVal newVal refName; do
build "$refName"
done
This will checkout and build the master branch when it is pushed. Everything else is ignored. That means the master branch is the “live” branch.
Note the use of JEKYLL_ENV=production
,
previously I mentioned
using jekyll.environment
in the base layout to determine if we’re in the live
site. By setting JEKYLL_ENV
before calling jekyll build
, we change that
variable from the default “development” to “production”. This will disable
things like the live.js script.
Per-branch builds
Sometimes I wanted to push something that wasn’t the master branch, usually to
show some in progress changes to a friend. To do this, I extended the script
first by removing the check for master
, then by changing the output directory
to be in a folder /wip
.
build(){
# ...
local out="$PUBLIC_WWW/wip/$branch"
if [[ "$branch" == "master" ]]; then
out="$PUBLIC_WWW"
fi
echo destination $out
mkdir -p "$out"
# ...
}
To avoid trashing /wip
when rebuilding master
, you also need to add the
following to the jekyll _config.yml
.
keep_files:
- wip
When you push a branch, that should then build that branch into a wip folder of the same name.
Because the work-in-progress sites now reside as a subfolder of the website, when defining urls we need to make sure we use the correct baseurl, and we need to set it building on the server.
The best solution I found for setting the baseurl was to just inject it into the
temporary checkout’s _config.yml
:
build(){
# ...
if [[ "$branch" != "master" ]]; then
echo "baseurl: \"/wip/$branch\"">>"$clone/_config.yml"
fi
# ...
}
It seems dirty, but it works.
Then in layouts and other website code, we need to use the baseurl variable.
<link rel="stylesheet" href="{{ site.baseurl }}/css/base.css" type="text/css" />
Now we can push any branch
git push deploy test-rebuild-layout
and see it at http://pddds.com/wip/test-rebuild-layout
(in theory).
Full script
I did a couple more changes after that including avoiding building tags and cleaning up deleted branches. Instead of cover them all, you can just read through the final hook.
#!/bin/bash
set -e
TMP_CLONE="$TMPDIR/website-git-clone"
PUBLIC_WWW="/home/public"
build(){
local branch="$1"
branch="${branch#refs/}"
branch="${branch#heads/}"
local clone="$TMP_CLONE"
if grep -q "^tags/" <<<"$branch"; then
return
fi
local out="$PUBLIC_WWW/wip/$branch"
if [[ "$branch" == "master" ]]; then
out="$PUBLIC_WWW"
fi
echo destination $out
mkdir -p "$out"
echo checking out
mkdir -p "$clone"
git --work-tree="$clone" checkout -f "$branch"
git --work-tree="$clone" reset --hard HEAD
echo destination $out
mkdir -p "$out"
echo building
if [[ "$branch" != "master" ]]; then
echo "baseurl: \"/wip/$branch\"">>"$clone/_config.yml"
fi
JEKYLL_ENV=production jekyll build -s "$clone" -d "$out"
}
delete(){
local branch="$1"
branch="${branch#refs/}"
branch="${branch#heads/}"
echo deleting $branch
rm -rf "$PUBLIC_WWW/wip/$branch"
}
while read oldVal newVal refName; do
if grep -q '^0*$' <<<$newVal; then
delete $refName
else
build "$refName"
fi
done
That’s it. Plenty more to add to the site (firstly pagination, the front pages is getting pretty long), and some less technical posts to write.