how I build this blog
• Author: bl0v3 •If you read this very text its safe to assume you have come across my blog. This article provides you with a little insight on how I created and manage it.
As if writing blogposts by itself wasn’t hard enough, managing the build environment that ends up generating these files you are viewing involves various other aspects to take care of as well. As writing html/css by hand shouldn’t exactly be the way to go on about specifically maintaining a blog lets say (such as in my case). Its not unlikely that one has looked at the various tools to essentially help you overcoming this burden.
In my case I chose to employ a combination of
- git
- the Zola static site generator
- nix
So what these tools essentially accomplish is:
Letting me create blogposts by creating a directory in /bl0v3.com/content/Blog/
such as zola-meets-nix
and then writing the actual posts by writing some markdown declarations into zola-meets-nix/index.md
lets say to generate the page you are currently viewing.
While so far all I have done is obtaining zolas source code and writing my blogposts into the respective markdown files. There is a actually little more to it as:
What if I wanted to edit my blog from multiple machines and track changes?
While I could simply put my blog into a git repository like I’ve done here (Contains what you are currently viewing) there are things I wanted to take into account that wouldn’t be solved by simply doing that
Because I’d have to ensure that:
- the version of zola used across the machines I’m making edits from remains the same
- That the build environment and dependencies used to build zola remain the same.
Also what if I wanted to host my blog on multiple platforms? For example at (bl0v3.com) or github.io. What if I maybe want to make use of tools other than zola to create pages/the content found on these pages? Like maybe I’d like to include some shaders compiled to webgl for interactive demos to reference in my blogposts.
With each further component I can think of that I may want to include in the site generation at some point in the future. The environment needed to create these posts would grow more and more complex. So I thought why not use nix since I already use that on all of my workstations/laptops or serves or at this point even on my phone via nix on droid or even mobile-nixos (sorta experimental still).
Which is why the repo linked above contains this flake.nix file. In essence this file defines the following function:
mkSite {name ? "bl0v3-website", url}
Which takes the arguments name
(which essentially just serves as a package name reference) and url
which would be the url to generate the site for. Thus the flake essentially outputs 2 “derivations”
{
packages = rec {
default = github;
github = mkSite {
name ="bl0v3-website-github";
url ="https://bolives-hax.github.io";
};
bl0v3_dot_com = mkSite {
name ="bl0v3-website-bl0v3_dot_com";
url ="https://bl0v3.com";
};
};
}
while I specify name
by using the ?
operator which essentially allows assigning a default value in case name
wasn’t specified upon calling mkSite{}
. url
still needs to explicitly defined upon calling mkSite
The flakes input parameters are:
{
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
flake-utils.url = "github:numtide/flake-utils";
website-src = {
flake = false;
url = "path:bl0v3.com";
};
duckquill-theme = {
type = "git";
flake = false;
url = "https://codeberg.org/daudix/duckquill.git";
};
};
}
with nixpkgs
providing the derivations/package definition for all packages contained within the nixpkgs
package collection (which for example is where nixos gets its packages from. But due to nix’s nature it works on a mac or non-nixos linux host just as well :3 since nix packages are pretty much build instructions rather than binary artifacts). duckquill-theme
is the theme i use and website-src
provides the actual source for the markdown files and config options used by zola. Additionally flake-utils
provides some helpers (more on that later).
Thus all that is left is combining the flakes inputs
and declarations to form the derivations passed via outputs
:
flake-utils.lib.eachDefaultSystem (system:
let
pkgs = import nixpkgs {
inherit system;
};
mkSite = {name ? "bl0v3-website", url}: with pkgs; pkgs.stdenv.mkDerivation {
inherit name;
src = website-src;
unpackPhase = ''
cp -r --no-preserve=mode $src source
mkdir -p source/themes/
cp -r --no-preserve=mode duckquill-theme source/themes/duckquill
cd source
echo -e "# The URL the site will be built for\nbase_url = \"url\"" | cat - config.toml
'';
buildPhase = ''
pkgs.zola/bin/zola build
'';
installPhase = ''
cp -r public $out
'';
};
in {
packages = rec {
default = github;
github = mkSite {
name =" bl0v3-website-github";
url ="https://bolives-hax.github.io";
};
bl0v3_dot_com = mkSite {
name =" bl0v3-website-bl0v3_dot_com";
url ="https://bl0v3.com";
};
};
# type nix develop .#
# and then in the shel you can use
# zola serve OR zola build
# NOTE that --base-url / --interface 0.0.0.0 --port $PORT are
# needed for zola serve to work correctly
devShells.default = with pkgs; mkShell {
buildInputs = [
zola
];
};
}
)
Let me break down this flake a little:
The eachDefaultSystem
function called via
flake-utils.lib.eachDefaultSystem (system:
essentially generates an attribute set referencing the various platforms/architectures supported by nix/nixpkgs by default (nix also supports more obscure platforms such as riscv or i386 and has an excellent cross compilation infrastructure but I reseve that topic for another blogpost that has yet to be written)
This could be done by hand by specifying
{
outputs.packages = {
x86_64-linux = "...";
aarch64-linux = "...";
x86_64-darwin = "...";
# etc
# ...
};
}
manually but I like keeping things short, reduce code duplication and have a somewhat future proof setup as maybe in the future another architecture gets adopted into said default architectures. Thus eachDefaultSystem
essentially merely generates these struct declaration prefixes and calls the function following (system:
which was written by me and essentially generates the derivation containing the actual build instructions for the blog using the various urls.
Note that system:
is passed as a function parameter and contains a string declaring the current system for which the attribute is being emitted. Thus the function within would essentially be called once with x86_64-linux
and once with x86_64-darwin
and so on.
mkSite {name,url}
ends up calling pkgs.stdenv.mkDerivation
like:
pkgs.stdenv.mkDerivation {
inherit name;
src = website-src;
unpackPhase = ''
cp -r --no-preserve=mode $src source
cp -r --no-preserve=mode duckquill-theme source/themes/duckquill
cd source
echo -e "# The URL the site will be built for\nbase_url = \"url\"" | cat - config.toml
'';
buildPhase = ''
pkgs.zola/bin/zola build
'';
installPhase = ''
cp -r public $out
'';
}
which essentially runs the build process, places the output artifacts in the corresponding So putting together these components pretty much creates the basis for building my blog, while currently it doesn’t really account too much for the shortcomings listed above. Introducing more components into the website build process is now easier than ever while also staying highly reproducible. If I now wanted to serve that site on a nixos host trough nginx, generate certificates and so on all id have to do is something like: by adding it to a module declaration contained within /etc/nixos in the same sense other nixos options are being set. Of course id need to provide the though thats relatively simple by adding it to my flake inputs or using To populate the github.io repo I do something like: while thats a little hacky it gets the job done for now but I plan take a different approach either way making use of my CI infrastructure but for now this gets the job done as all id have to do to populate/update that site is run: after cloning this repository but ofc that should be automated eventually and be part of my nix powered CI/CD./nix/store/
{
services.nginx = {
enable = true;
virtualHosts."blog.example.com" = {
enableACME = true;
forceSSL = true;
root = "bl0v3_website.packages.system.bl0v3_dot_com";
};
};
security.acme.certs = {
"blog.example.com".email = "youremail@address.com";
};
}
bl0v3_website
definition thats being referenced in{
root = "bl0v3_website.packages.system.bl0v3_dot_com";
}
fetchGit
on non flake systems. (there are very valid reasons one would not want to use flakes but by the time these start to be a concern, you most likely be aware of them already. Otherwise I’d say its fine to use flakes as they do also solve certain issues. In the end its just personal preference){
inputs = {
bl0v3_static = {
url = "github:bolives-hax/bl0v3.com-static";
};
};
}
{
inputs = {
bl0v3_static = {
url = "github:bolives-hax/bl0v3.com-static";
};
flake-utils.url = "github:numtide/flake-utils";
nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-unstable";
};
outputs = {self,bl0v3_static,flake-utils,nixpkgs}: flake-utils.lib.eachDefaultSystem(system:
let
pkgs = import nixpkgs {inherit system;};
in {
packages.default = pkgs.writeShellScriptBin "update-site" ''
SITE_PATH="bl0v3_static.packages.system.github"
echo $SITE_PATH
if [ -z "$1" ]; then
echo usage: nix run .# \"git commit message\"
else
cp -Lr --no-preserve=mode $SITE_PATH site
cp -r .git flake.nix README.md site/
cd site
git add .
git commit -m \"$1\"
git push
cd ../
rm -rf site
fi
'';
});
}