summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Makefile6
-rw-r--r--default.nix9
-rw-r--r--src/_config.yml2
-rw-r--r--src/_includes/header.html4
-rw-r--r--src/_posts/2021-01-23-goodbye-github-pages.md239
5 files changed, 249 insertions, 11 deletions
diff --git a/Makefile b/Makefile
index 84ee695..998bdd4 100644
--- a/Makefile
+++ b/Makefile
@@ -6,12 +6,10 @@ install: result
clean:
rm result
+ rm -rf _site
serve:
nix-shell -A serve
update:
- nix-shell -p bundler --run 'bundler update'
-
-lock:
- nix-shell -p bundler -p bundix --run 'bundler lock; bundler package --no-install --path vendor; bundix; rm -rf .bundle vendor'
+ nix-shell -p bundler --run 'bundler update; bundler lock; bundix; rm -rf .bundle vendor'
diff --git a/default.nix b/default.nix
index 95bfc26..19de15d 100644
--- a/default.nix
+++ b/default.nix
@@ -12,18 +12,17 @@
in
{
build = derivation {
- system = system;
+ inherit jekyll_env system;
name = "mediocre-blog";
builder = "${pkgs.bash}/bin/bash";
args = [ ./build.sh ];
src = ./src;
stdenv = pkgs.stdenv;
- inherit jekyll_env;
};
- serve = pkgs.stdenv.mkDerivation rec {
- name = "jekyll_env";
+ serve = pkgs.stdenv.mkDerivation {
+ name = "mediocre-blog-shell";
# glibcLocales is required so to fill in LC_ALL and other locale
# related environment vars. Without those jekyll's scss compiler
@@ -36,6 +35,8 @@
exec ${jekyll_env}/bin/jekyll serve -s ./src -d ./_site -w -I -D -H 0.0.0.0
'';
};
+
+ env = jekyll_env;
}
diff --git a/src/_config.yml b/src/_config.yml
index c8b0980..acca4c8 100644
--- a/src/_config.yml
+++ b/src/_config.yml
@@ -5,7 +5,7 @@ description: >-
A mix of tech, art, travel, and who knows what else.
baseurl: "" # the subpath of your site, e.g. /blog
url: https://blog.mediocregopher.com # the base hostname & protocol for your site, e.g. http://example.com
-repository: mediocregopher/blog.mediocregopher.com
+repository: https://github.com/mediocregopher/blog.mediocregopher.com
twitter_username: mediocre_gopher
github_username: mediocregopher
rss: rss
diff --git a/src/_includes/header.html b/src/_includes/header.html
index 888a8dc..a6b9e30 100644
--- a/src/_includes/header.html
+++ b/src/_includes/header.html
@@ -1,6 +1,6 @@
<header id="title-header" role="banner">
<div class="row">
- <div class="seven columns">
+ <div class="seven columns" style="margin-bottom: 3rem;">
<h1 class="title">
<a href="{{ "/" | relative_url }}">{{ site.title | escape }}</a>
</h1>
@@ -20,7 +20,7 @@
</div>
</div>
{%- if!page.nofollow != true %}
- <div class="five columns light" style="text-align: right">
+ <div class="five columns light">
<span style="display:block; margin-bottom:0.5rem;">Get notified when new posts are published!</span>
<a href="{{ "/follow.html" | relative_url }}"><button class="button-primary">
<i class="fab fa-twitter"></i>
diff --git a/src/_posts/2021-01-23-goodbye-github-pages.md b/src/_posts/2021-01-23-goodbye-github-pages.md
new file mode 100644
index 0000000..7f19ec7
--- /dev/null
+++ b/src/_posts/2021-01-23-goodbye-github-pages.md
@@ -0,0 +1,239 @@
+---
+title: >-
+ Goodbye, Github Pages
+description: >-
+ This blog is no longer sponsored by Microsoft!
+---
+
+Slowly but surely I'm working on moving my digital life back to being
+self-hosted, and this blog was an easy low-hanging fruit to tackle. Previously
+the blog was hosted on Github Pages, which was easy enough but also in many ways
+restricting. By self-hosting I'm able to have a lot more control over the
+generation, delivery, and functionality of the blog.
+
+For reference you can find the source code for the blog at
+[{{site.repository}}]({{site.repository}}). Yes, it will one day be hosted
+elsewhere as well.
+
+## Nix
+
+Nix is something I'm slowly picking up, but the more I use it the more it grows
+on me. Rather than littering my system with ruby versions and packages I'll
+never otherwise use, nix allows me to create a sandboxed build pipeline for the
+blog with perfectly reproducible results.
+
+The first step in this process is to take the blog's existing `Gemfile.lock` and
+turn it into a `gemset.nix` file, which is essentially a translation of the
+`Gemfile.lock` into a file nix can understand. There's a tool called
+[bundix][bundix] which does this, and it can be used from a nix shell without
+having to actually install anything:
+
+```
+ nix-shell -p bundix --run 'bundix'
+```
+
+The second step of using nix is to set up a nix expression in the file
+`default.nix`. This will actually build the static files. As a bonus I made my
+expression to also allow for serving the site locally with dynamic updating
+everytime I change a source file. My `default.nix` looks like this:
+
+```
+{
+ # pkgs refers to all "builtin" nix pkgs and utilities. By importing from a
+ # URL I'm able to always pin this default.nix to a specific version of those
+ # packages.
+ pkgs ? import (fetchTarball "https://github.com/NixOS/nixpkgs/archive/cd63096d6d887d689543a0b97743d28995bc9bc3.tar.gz") {},
+ system ? builtins.currentSystem,
+}:
+
+ let
+ # bundlerEnv looks for a Gemfile, Gemfile.lock, and gemset.nix inside
+ # gemdir, and derives a package containing ruby and all desired gems.
+ ruby_env = pkgs.bundlerEnv {
+ name = "ruby_env";
+ ruby = pkgs.ruby;
+ gemdir = ./.;
+ };
+ in
+ {
+ # build will derive a package which contains the generated static
+ # files of the blog. It uses the build.sh file (provided below) to
+ # do this.
+ build = derivation {
+ name = "mediocre-blog";
+
+ # The build.sh file (source provided below) is executed in order
+ # to actually build the site.
+ builder = "${pkgs.bash}/bin/bash";
+ args = [ ./build.sh ];
+
+ # ruby_env is provided as an input to build.sh so that it can
+ # use jekyll, and the src directory is provided so it can access
+ # the blog's source files. system is required by the derivation
+ # function, and stdenv provides standard utilities to build.sh.
+ inherit ruby_env system;
+ src = ./src;
+ stdenv = pkgs.stdenv;
+ };
+
+ # serve will derive an environment specifically tailored for being
+ # run in a nix-shell. The resulting shell will have ruby_env
+ # provided for it, and will automatically run the `jekyll serve`
+ # command to serve the blog locally.
+ serve = pkgs.stdenv.mkDerivation {
+ name = "mediocre-blog-shell";
+
+ # glibcLocales is required so to fill in LC_ALL and other locale
+ # related environment vars. Without those jekyll's scss compiler
+ # fails.
+ #
+ # TODO probably get rid of the scss compiler.
+ buildInputs = [ ruby_env pkgs.glibcLocales ];
+
+ shellHook = ''
+ exec ${ruby_env}/bin/jekyll serve -s ./src -d ./_site -w -I -D
+ '';
+ };
+ }
+```
+
+(Nix is a bit tricky to learn, but I highly recommend chapters 14 and 15 of [the
+nix manual][manual] for an overview of the language itself, if nothing else.)
+
+The `build.sh` used by the nix expression to actually generate the static files
+looks like this:
+
+```bash
+# stdenv was given a dependency to build.sh, and so build.sh can use it to
+# source in utilities like mkdir, which it needs.
+source $stdenv/setup
+set -e
+
+# Set up the output directory. nix provides the $out variable which will be the
+# root of the derived package's filesystem, but for simplicity later we want to
+# output the site within /var/www.
+d="$out/var/www/blog.mediocregopher.com"
+mkdir -p "$d"
+
+# Perform the jekyll build command. Like stdenv the ruby_env was given as a
+# dependency to build.sh, so it has to explicitly use it to have access to
+# jekyll. src is another explicit dependency which was given to build.sh, and
+# contains all the actual source files within the src directory of the repo.
+$ruby_env/bin/jekyll build -s "$src" -d "$d"
+```
+
+With these pieces in place I can easily regenerate the site like so:
+
+```
+nix-build -A build
+```
+
+Once run the static files will exist within a symlink called `result` in the
+project's root. Within the symlink will be a `var/www/blog.mediocregopher.com`
+tree of directories, and within that will be the generated static files, all
+without ever having to have installed ruby.
+
+The expression also allows me to serve the blog while I'm working on it. Doing
+so looks like this:
+
+```
+nix-shell -A serve
+```
+
+When run I get a normal jekyll process running in my `src` directory, serving
+the site in real-time on port 4000, once again all without ever installing ruby.
+
+As a final touch I introduced a simple `Makefile` to my repo to wrap these
+commands, because even these were too much for me to remember:
+
+```
+result:
+ nix-build -A build
+
+install: result
+ nix-env -i "$$(readlink result)"
+
+clean:
+ rm result
+ rm -rf _site
+
+serve:
+ nix-shell -A serve
+
+update:
+ nix-shell -p bundler --run 'bundler update; bundler lock; bundix; rm -rf .bundle vendor'
+```
+
+We'll look at that `install` target in the next section.
+
+## nginx
+
+So now I have the means to build my site quickly, reliably, and without
+cluttering up the rest of my system. Time to actually serve the files.
+
+My home server has a docker network which houses most of my services that I run,
+including nginx. nginx's primary job is to listen on ports 80 and 443, accept
+HTTP requests, and direct those requests to their appropriate service based on
+their `Host` header. nginx is also great at serving static content from disk, so
+I'll take advantage of that for the blog.
+
+The one hitch is that nginx is currently running within a docker container,
+as are all my other services. Ideally I would:
+
+* Get rid of the nginx docker container.
+* Build a nix package containing nginx, all my nginx config files, and the blog
+ files themselves.
+* Run that directly.
+
+Unfortunately extracting nginx from docker is dependent on doing so for all
+other services as well, or at least on running all services on the host network,
+which I'm not prepared to do yet. So for now I've done something janky.
+
+If you look at the `Makefile` above you'll notice the `install` target. What
+that target does is to install the static blog files to my nix profile, which
+exists at `~/.nix-profile`. nix allows any package to be installed to a profile
+in this way. All packages within a profile are independent and can be added,
+updated, and removed atomically. By installing the built blog package to my
+profile I make it available at `~/.nix-profile/var/www/blog.mediocregopher.com`.
+
+So to serve those files via nginx all I need to do is add a read-only volume to
+the container...
+
+```
+-v $HOME/.nix-profile/var/www/blog.mediocregopher.com:/var/www/blog.mediocregopher.com:ro \
+```
+
+...add a new virtual host to my nginx config...
+
+```
+server {
+ listen 80;
+ server_name blog.mediocregopher.com;
+ root /var/www/blog.mediocregopher.com;
+}
+```
+
+...and finally direct the `blog` A record for `mediocregopher.com` to my home
+server's IP. Cloudflare will handle TLS on port 443 for me in this case, as well
+as hide my home IP, which is prudent.
+
+## Deploying
+
+So now it's time to publish this new post to the blog, what are the actual
+steps? It's as easy as:
+
+```
+make clean install
+```
+
+This will remove any existing `result`, regenerate the site (with the new post)
+under a new symlink, and install/update that newer package to my nix profile,
+overwriting the previous package which was there.
+
+And that's it! Nix is a cool tool that I'm still getting the hang of, but
+hopefully this post might be useful to anyone else thinking of self-hosting
+their site.
+
+[jekyll]: https://jekyllrb.com/
+[bundix]: https://github.com/nix-community/bundix
+[manual]: https://nixos.org/manual/nix/stable/#chap-writing-nix-expressions