diff --git a/blog/nix-flakes-2-2022-02-27.markdown b/blog/nix-flakes-2-2022-02-27.markdown
new file mode 100644
index 0000000..f75ba98
--- /dev/null
+++ b/blog/nix-flakes-2-2022-02-27.markdown
@@ -0,0 +1,570 @@
+---
+title: "Nix Flakes: Packages and How to Use Them"
+date: 2022-02-27
+tags:
+ - nix
+ - nixos
+ - docker
+ - systemd
+series: nix-flakes
+vod:
+ twitch: https://www.twitch.tv/videos/1409855764
+ youtube: https://youtu.be/eUFBD-6yAWQ
+---
+
+
+
+[Nix flakes are still marked as experimental. This documentation has a small
+chance of bitrotting. I will make every attempt to update it if things change,
+however flakes have been fairly consistent for a few years
+now.](conversation://Cadey/coffee)
+
+
+
+[What is a package? I've seen this term thrown around with phrases like "Nix is a
+package manager" or "language-specific package manager" or even "download the
+debian package and install it", but it's not really clear to me what a package
+is. What is a package?](conversation://Mara/hmm)
+
+A package is a bundle of files. These files could be program executables,
+resources such as stylesheets or images, or even a container image. Most of the
+time you don't deal with packages directly and instead you use a _package
+manager_ (a program whose sole goal in life is to deal with packages) to do
+actions for you. This post is going to cover how to define packages in Nix and
+how Nix flakes let you manage multiple packages per project more easily.
+
+## What is a Package?
+
+In Nix, you build packages by creating _derivations_ that define the build steps
+and associated inputs (such as the compiler) to end up with the resulting
+outputs (derivation being the product of deriving something). Consider a package
+like this:
+
+```nix
+# hello-shell.nix
+with import { };
+stdenv.mkDerivation {
+ name = "hello-HEAD";
+ src = ./.;
+ installPhase = ''
+ echo "Hello" > $out
+ '';
+}
+```
+
+Then we can build this package with `nix-build hello-shell.nix` and a `result`
+symlink will show up in your current working directory. Then you can view what
+it says with `cat`:
+
+```console
+$ cat ./result
+Hello
+```
+
+This is all it takes to make a Nix package. You need to name the package, give
+it input source code somehow, and potentially give it build instructions.
+Everything else we'll cover today will build on top of this.
+
+Let's look back at the Go [example
+package](https://github.com/Xe/gohello/blob/caf54cdff7d8dd9bd9df4b3b783a72fe75c9a11e/flake.nix#L31-L54)
+I walked us through in [the last
+post](https://christine.website/blog/nix-flakes-1-2022-02-21):
+
+```nix
+# ...
+packages = forAllSystems (system:
+ let pkgs = nixpkgsFor.${system};
+ in {
+ go-hello = pkgs.buildGoModule {
+ pname = "go-hello";
+ inherit version;
+ # In 'nix develop', we don't need a copy of the source tree
+ # in the Nix store.
+ src = ./.;
+
+ # This hash locks the dependencies of this package. It is
+ # necessary because of how Go requires network access to resolve
+ # VCS. See https://www.tweag.io/blog/2021-03-04-gomod2nix/ for
+ # details. Normally one can build with a fake sha256 and rely on native Go
+ # mechanisms to tell you what the hash should be or determine what
+ # it should be "out-of-band" with other tooling (eg. gomod2nix).
+ # To begin with it is recommended to set this, but one must
+ # remeber to bump this hash when your dependencies change.
+ vendorSha256 =
+ "sha256-pQpattmS9VmO3ZIQUFn66az8GSmB4IvYhTTCFn6SUmo=";
+ };
+ });
+# ...
+```
+
+This uses a different builder, one called
+[`pkgs.buildGoModule`](https://nixos.org/manual/nixpkgs/stable/#ssec-language-go).
+This is like the `stdenv.mkDerivation` builder, except it is explicitly made to
+handle Go projects. There are some other flags that you can set in
+`buildGoModule` that can be useful. You can see examples in the NixOS manual
+page [here](https://nixos.org/manual/nixpkgs/stable/#ssec-language-go).
+
+Another useful builder is [Naersk](https://github.com/nix-community/naersk).
+Naersk will automatically derive build instructions for Rust projects using the
+`Cargo.toml` and `Cargo.lock` files. This means that your build step can look as
+small as this:
+
+```nix
+naersk-lib.buildPackage ./.
+```
+
+[You can think of these builders as templates for doing larger builds. This is
+kinda like the ONBUILD
+Dockerfile instruction, but it isn't limited to
+Docker. The main difference is that Nix builds are more like functions (inputs
+and outputs) and Docker builds focus on the individual commands you run to get
+the result you want. Both eventually compile down to shell commands
+anyways!](conversation://Mara/hacker)
+
+## A More Useful Package
+
+This "hello world" program isn't very useful on its own, however we can use it
+as the basis for making something a bit more useful. I have made a template for
+a "Hello world" HTTP server
+[here](https://github.com/Xe/templates/tree/main/go-web-server). Let's make a
+new folder for it and then initialize it:
+
+[If you want to make your own templates, see how to do that here.](conversation://Mara/hacker)
+
+```shell
+mkdir -p ~/tmp/gohello-http
+cd ~/tmp/gohello-http
+git init
+nix flake init -t github:Xe/templates#go-web-server
+```
+
+[You may see a message from direnv about
+needing to approve its content. This will use Nix flake's cached interpreter to
+give you all the advantages of something like Lorri without having to
+install Lorri.](conversation://Mara/hacker)
+
+Then make an initial commit and run it:
+
+```shell
+git add .
+git commit -sm "initial commit"
+nix build
+./result/bin/web-server
+```
+
+[Why are you using `git add .` everywhere? Shouldn't the files be picked up
+implicitly?](conversation://Mara/hmm)
+
+[Not always. Nix flakes only deals with files that are tracked by git when you
+use it in a git repository. This means that if you want the changes to be
+observed by Nix, you need to add them to git somehow. `git add` is good enough
+for this.](conversation://Cadey/enby)
+
+Or you can run it directly with `nix run`:
+
+```shell
+nix run
+```
+
+## Docker Images
+
+Most of the time you will build software with Nix, however that doesn't stop you
+from building things like Docker images with Nix. Remember that you can have the
+output of any shell commands be run in a Nix build (the only catch is that they
+can't access the internet directly), so you can build a Docker image out of that
+web server template by defining another package:
+
+```nix
+# flake.nix
+
+# after defaultPackage
+packages = {
+ docker = let
+ web = self.defaultPackage.${system};
+ in pkgs.dockerTools.buildLayeredImage {
+ name = web.pname;
+ tag = web.version;
+ contents = [ web ];
+
+ config = {
+ Cmd = [ "/bin/web-server" ];
+ WorkingDir = "/";
+ };
+ };
+};
+```
+
+This will build a Docker image with the web-server binary in it. To build it,
+run these commands:
+
+```shell
+git add .
+nix build .#docker
+```
+
+[What's with that last argument to `nix build`, won't that be read as a shell
+comment?](conversation://Mara/hmm)
+
+[It's a reference to the package in the flake. Shell only parses comments when
+the `#` is the first character after whitespace, so this is more of a URL
+fragment than a comment. It's telling `nix build` to build the flake package
+named `docker`.](conversation://Cadey/enby)
+
+It will put the resulting docker image in `./result`. To load it into docker use
+the following command:
+
+```console
+$ docker load < result
+Loaded image: web-server:20220227
+```
+
+[Your image tag may differ depending on when you build this
+image. This is deterministic because that date is derived from the date that the
+current git commit was made.](conversation://Mara/happy)
+
+Then you can run it with `docker run`:
+
+```shell
+docker run -itp 3031:3031 web-server:20220227
+```
+
+Then poke it with curl:
+
+```console
+$ curl http://[::]:3031
+hello from nix!
+```
+
+You can push this image to the Docker hub like any other image. Another cool
+thing about this is that when you update the program, it'll only actually load
+the images that changed. Let's edit the hello world message:
+
+```go
+http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
+ fmt.Fprintln(w, "hello from nix building a docker image!")
+})
+```
+
+And then re-build and load it into Docker:
+
+```shell
+git add .
+nix build .#docker
+docker load < result
+```
+
+[Woah, when I did that it only updated 2 layers. The first time that I loaded it
+there were something like 7 layers. What's up with
+that?](conversation://Mara/hmm)
+
+[When you use `buildLayeredImage`, each Nix package that contributes to the
+image gets put in its own Docker layer. This means that only the things that
+have changed actually need to be considered, so when you push an updated image
+to another machine, the only things that will actually be pushed are the
+application binary and the symlink farm pointing to the `contents` of the Docker
+image.](conversation://Cadey/enby)
+
+## systemd Portable Services
+
+[systemd Portable Services](https://systemd.io/PORTABLE_SERVICES/) function like
+Docker, but they work at the systemd level and allow you to integrate into
+systemd instead of running on the side of it. This gives you access to systemd's
+readiness signaling, logging pipeline and dependency graph so that you can
+integrate like a native service. They are like containers, but without a lot of
+the headaches around networking, stateful storage and logging. They are just
+systemd services at their core.
+
+[These are kinda like Ubuntu's Snaps or Flatpaks, but they operate purely at the
+system level and are focused at providing things for system services instead of
+user-facing applications. Ubuntu's Snaps do let you create system services, but
+they are basically exclusively used on Ubuntu. systemd Portable Services let you
+target more than just Ubuntu. In the next few years with more releases of
+systemd, Portable Services should be easier to use and will be more integrated
+with the system than Docker is.](conversation://Mara/hacker)
+
+There is currently an [open pull request](https://systemd.io/PORTABLE_SERVICES/)
+for adding Portable Service building support to nixpkgs, however we can mess
+around with it today thanks to [my portable-svc
+overlay](https://tulpa.dev/cadey/portable-svc) that copies in the contents of
+that pull request.
+
+[In Nix, an overlay is a set of additional packages or functions that is put on
+top of nixpkgs. This overlay defines the `portableService` function that is
+needed to build portable services.](conversation://Mara/hacker)
+
+To make this into a portable service, first we need to add my overlay to the
+flake inputs:
+
+```nix
+# flake.nix
+inputs = {
+ nixpkgs.url = "nixpkgs/nixos-unstable";
+ utils.url = "github:numtide/flake-utils";
+ portable-svc.url = "git+https://tulpa.dev/cadey/portable-svc.git?ref=main";
+};
+```
+
+Then add it as an argument to the `outputs` function:
+
+```nix
+outputs = { self, nixpkgs, utils, portable-svc }:
+```
+
+And then change how we are importing the `pkgs` variable. The `pkgs` variable
+we're currently using is imported like this:
+
+```nix
+let pkgs = nixpkgs.legacyPackages.${system};
+```
+
+This works, however there isn't a way to specify an overlay into this. We need
+to change this into a manual import of nixpkgs with the overlay specified, like
+this:
+
+```nix
+let pkgs = import nixpkgs {
+ overlays = [ portable-svc.overlay ];
+ inherit system;
+};
+```
+
+This will let us use the `portableService` function in Nix package definitions.
+
+Next we need to make a systemd service unit for the web server. The exact path
+to the program binary can and will change with every build, so it would be good
+to have this templated. Make a folder called `systemd`:
+
+```shell
+mkdir systemd
+```
+
+And put the following contents in `systemd/web-server.service.in`:
+
+```systemd
+[Unit]
+Description=A web service
+
+[Service]
+DynamicUser=yes
+ExecStart=@web@/bin/web-server
+
+[Install]
+WantedBy=multi-user.target
+```
+
+Then under the docker package definition, add the package that will template out
+the systemd unit:
+
+```nix
+web-service = pkgs.substituteAll {
+ name = "web-server.service";
+ src = ./systemd/web-server.service.in;
+ web = self.defaultPackage.${system};
+};
+```
+
+You can build it with `nix build .#web-service`, the output will look something
+like this:
+
+```systemd
+[Unit]
+Description=A web service
+
+[Service]
+DynamicUser=yes
+ExecStart=/nix/store/yl863jm907wfr7gq9j0c4bd3d4bdc4vp-web-server-20220227/bin/web-server
+
+[Install]
+WantedBy=multi-user.target
+```
+
+[The `@web@` in the template was replaced with the nix store path for the web
+server!](conversation://Mara/happy)
+
+Then you can add the bit that builds the portable service:
+
+```nix
+portable = let
+ web = self.defaultPackage.${system};
+in pkgs.portableService {
+ inherit (web) version;
+ name = web.pname;
+ description = "A web server";
+ units = [ self.packages.${system}.web-service ];
+};
+```
+
+Then you can build it with `nix build`:
+
+```shell
+nix build .#portable
+```
+
+And then take a look at `./result`:
+
+```console
+$ file $(readlink ./result)
+/nix/store/1da6b90i75n03kqlzzfdwxii0j0bzxaf-web-server_20220227.raw:
+Squashfs filesystem,
+little endian,
+version 4.0,
+xz compressed,
+9555806 bytes,
+2010 inodes,
+blocksize: 1048576 bytes,
+created: Tue Jan 1 00:00:00 1980
+```
+
+
+
+At the time of writing this article, the most reliable way to test portable
+services is to use Arch Linux. So you could use something like
+[waifud](https://github.com/Xe/waifud) to spin up an Arch Linux VM:
+
+```console
+$ waifuctl create -d arch -h logos -s 20
+created instance jangmo-o on logos
+jangmo-o: running
+jangmo-o: init: IP address: 10.77.129.208
+```
+
+Then copy it over with `scp`:
+
+```console
+$ scp (readlink ./result) xe@10.77.129.208:web-server_20220227.raw
+```
+
+
+
+Then you can use `portablectl` to attach it to the system:
+
+```console
+$ sudo portablectl attach ./web-server_20220227.raw
+[...]
+Created symlink /etc/portables/web-server_20220227.raw → /home/xe/web-server_20220227.raw.
+```
+
+And then start it like any systemd service:
+
+```console
+$ sudo systemctl start web-server
+```
+
+[If you want the service to start automatically, add `--enable --now` to the
+`portablectl attach` command. That will enable the service in systemd and then
+start it, like when you run `systemctl enable --now
+something.service`.](conversation://Mara/hacker)
+
+And then inspect the service's status with `systemctl`:
+
+```console
+$ sudo systemctl status web-server
+● web-server.service - A web service
+ Loaded: loaded (/etc/systemd/system.attached/web-server.service; disabled; vendor preset: disabled)
+ Drop-In: /etc/systemd/system.attached/web-server.service.d
+ └─10-profile.conf, 20-portable.conf
+ Active: active (running) since Sun 2022-02-27 18:21:01 UTC; 20s ago
+ Main PID: 960 (web-server)
+ Tasks: 5 (limit: 513)
+ Memory: 8.1M
+ CPU: 189ms
+ CGroup: /system.slice/web-server.service
+ └─960 /nix/store/yl863jm907wfr7gq9j0c4bd3d4bdc4vp-web-server-20220227/bin/web-server
+
+Feb 27 18:21:01 jangmo-o systemd[1]: Started A web service.
+Feb 27 18:21:01 jangmo-o web-server[960]: 2022/02/27 18:21:01 listening for HTTP on :3031
+```
+
+And finally poke it with curl:
+
+```console
+$ curl http://[::]:3031
+hello from nix building a docker image!
+```
+
+[That ain't Docker, chief!](conversation://Numa/delet)
+
+[I know, I know, I didn't adjust the message from when I wrote the Docker
+example.](conversation://Cadey/facepalm)
+
+And then you can change the handler to something like:
+
+```go
+http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
+ fmt.Fprintf(w, "PORTABLE=%s\n", os.Getenv("PORTABLE"))
+})
+```
+
+Rebuild the image with `nix build`:
+
+```console
+$ git add .
+$ nix build .#portable
+```
+
+Copy it to the arch VM with `scp`:
+
+```console
+$ scp (readlink ./result) xe@10.77.129.208:web-server_20220227.raw
+```
+
+And finally run `portablectl reattach` to upgrade it:
+
+```console
+$ sudo portablectl reattach --now ./web-server_20220227.raw
+Queued /org/freedesktop/systemd1/job/858 to call RestartUnit on portable service
+web-server.service.
+```
+
+Then you can see that it restarted the unit with `systemctl status`:
+
+```console
+$ sudo systemctl status web-server
+● web-server.service - A web service
+ Loaded: loaded (/etc/systemd/system.attached/web-server.service; disabled; vendor preset: disabled)
+ Drop-In: /etc/systemd/system.attached/web-server.service.d
+ └─10-profile.conf, 20-portable.conf
+ Active: active (running) since Sun 2022-02-27 18:30:04 UTC; 37s ago
+ Main PID: 1074 (web-server)
+ Tasks: 6 (limit: 513)
+ Memory: 8.1M
+ CPU: 182ms
+ CGroup: /system.slice/web-server.service
+ └─1074 /nix/store/j1mfz3ydn13qmvcgrql33zi0dwb3x7dk-web-server-20220227/bin/web-server
+
+Feb 27 18:30:04 jangmo-o systemd[1]: Started A web service.
+Feb 27 18:30:04 jangmo-o web-server[1074]: 2022/02/27 18:30:04 listening for HTTP on :3031
+```
+
+And finally poke it with curl:
+
+```console
+$ curl http://[::]:3031
+PORTABLE=web-server_20220227.raw
+```
+
+And there you go! Nix created a portable system service, we spawned it on a
+newly created Arch Linux VM and then were able to update it so that we could
+replace the message.
+
+---
+
+Nix builds can do more than just turn code into software. They can create Docker
+images, Portable Services, virtual machine images and more. The only real limit
+is what you can imagine.
+
+Flakes make it easier to pull in and munge about packages. Before flakes you'd
+need to have a few `.nix` files like `docker.nix` for the docker image and
+`portable.nix` for the portable service. You'd also have to pull in something
+like [Niv](https://github.com/nmattia/niv) to make sure everything uses the same
+version of nixpkgs, and even then it's opt-in, not opt-out, so it's easy to mess
+things up and not use the pinned versions of things. Flakes make that explicit
+behavior implicit, so you can't bring in dependencies you aren't aware of.
+
+If you want to see the code repo I developed while writing this post, see
+[cadey/gohello-http](https://tulpa.dev/cadey/gohello-http) on my git server.
+
+Thanks for reading!
diff --git a/src/post/frontmatter.rs b/src/post/frontmatter.rs
index 595079f..fa4c65c 100644
--- a/src/post/frontmatter.rs
+++ b/src/post/frontmatter.rs
@@ -13,6 +13,13 @@ pub struct Data {
pub thumb: Option,
pub show: Option,
pub redirect_to: Option,
+ pub vod: Option,
+}
+
+#[derive(Eq, PartialEq, Deserialize, Default, Debug, Serialize, Clone)]
+pub struct Vod {
+ pub twitch: String,
+ pub youtube: String,
}
enum State {
diff --git a/templates/blogpost.rs.html b/templates/blogpost.rs.html
index 8ace300..3b60689 100644
--- a/templates/blogpost.rs.html
+++ b/templates/blogpost.rs.html
@@ -68,6 +68,10 @@
+@if post.front_matter.vod.is_some() {
+
This post was written live on Twitch. You can check out the stream recording on Twitch here and on YouTube here.