diff --git a/blog/nix-flakes-1-2022-02-21.markdown b/blog/nix-flakes-1-2022-02-21.markdown
index ae87f3d..7b20dc1 100644
--- a/blog/nix-flakes-1-2022-02-21.markdown
+++ b/blog/nix-flakes-1-2022-02-21.markdown
@@ -136,7 +136,10 @@ Then you can look at `flake.nix` to see what's up:
pkgs = nixpkgsFor.${system};
in
{
- go-hello = pkgs.buildGoModule {
+ # The default package for 'nix build'. This makes sense if the
+ # flake provides only one package or there is a clear "main"
+ # package.
+ default = pkgs.buildGoModule {
pname = "go-hello";
inherit version;
# In 'nix develop', we don't need a copy of the source tree
@@ -156,11 +159,6 @@ Then you can look at `flake.nix` to see what's up:
vendorSha256 = "sha256-pQpattmS9VmO3ZIQUFn66az8GSmB4IvYhTTCFn6SUmo=";
};
});
-
- # The default package for 'nix build'. This makes sense if the
- # flake provides only one package or there is a clear "main"
- # package.
- defaultPackage = forAllSystems (system: self.packages.${system}.go-hello);
};
}
```
@@ -197,7 +195,6 @@ Let's take a closer look at the higher level things in the flake:
outputs = { self, nixpkgs }: {
packages = { ... };
- defaultPackage = { ... };
};
}
```
@@ -462,7 +459,7 @@ nixosModules.bot = { config, lib, ... }: {
Group = "mara-bot";
Restart = "always";
WorkingDirectory = "/var/lib/mara-bot";
- ExecStart = "${self.defaultPackage."${system}"}/bin/mara";
+ ExecStart = "${self.packages."${system}".default}/bin/mara";
};
};
};
diff --git a/blog/nix-flakes-2-2022-02-27.markdown b/blog/nix-flakes-2-2022-02-27.markdown
index b820885..b163fcf 100644
--- a/blog/nix-flakes-2-2022-02-27.markdown
+++ b/blog/nix-flakes-2-2022-02-27.markdown
@@ -19,6 +19,10 @@ 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)
+[EDIT(20220327 14:13): A previous version of this article said to use
+`defaultPackage` for the default package. This is deprecated and you should use
+`packages.default` instead.](conversation://Cadey/coffee)
+
[What is a package? I've seen this term thrown around with phrases like "Nix is a
@@ -180,10 +184,10 @@ web server template by defining another package:
```nix
# flake.nix
-# after defaultPackage
packages = {
+ default = ...;
docker = let
- web = self.defaultPackage.${system};
+ web = self.packages.${system}.default;
in pkgs.dockerTools.buildLayeredImage {
name = web.pname;
tag = web.version;
@@ -373,7 +377,7 @@ the systemd unit:
web-service = pkgs.substituteAll {
name = "web-server.service";
src = ./systemd/web-server.service.in;
- web = self.defaultPackage.${system};
+ web = self.packages.${system}.default;
};
```
@@ -399,7 +403,7 @@ Then you can add the bit that builds the portable service:
```nix
portable = let
- web = self.defaultPackage.${system};
+ web = self.packages.${system}.default;
in pkgs.portableService {
inherit (web) version;
name = web.pname;
diff --git a/blog/nix-flakes-3-2022-04-07.markdown b/blog/nix-flakes-3-2022-04-07.markdown
new file mode 100644
index 0000000..342b29f
--- /dev/null
+++ b/blog/nix-flakes-3-2022-04-07.markdown
@@ -0,0 +1,443 @@
+---
+title: "Nix Flakes: Exposing and using NixOS Modules"
+date: 2022-04-07
+series: nix-flakes
+tags:
+ - nixos
+vod:
+ twitch: https://www.twitch.tv/videos/1437346416
+ youtube: https://youtu.be/wCZ9SwmgSck
+---
+
+Nix flakes allow you to expose NixOS modules. NixOS modules are templates for
+system configuration and they are the basis of how you configure NixOS. Today
+we're going to take our Nix flake [from the last
+article](/blog/nix-flakes-2-2022-02-27) and write a NixOS module for it so that
+we can deploy it to a container running locally. In the next post we will deploy
+this to a server.
+
+[If you haven't read the other articles in
+this series, you probably should. This article builds upon the previous
+ones.](conversation://Mara/hacker)
+
+NixOS modules are building blocks that let you configure NixOS servers. Modules
+expose customizable options that expand out into system configuration.
+Individually, each module is fairly standalone and self-contained, but they
+build up together into your server configuration like a bunch of legos build
+into a house. Each module describes a subset of your desired system
+configuration and any options relevant to that configuration.
+
+[You can think about them like Ansible playbooks, but NixOS modules describe the
+desired end state instead of the steps you need to get to that end
+state. It's the end result of evaluating all of your options against all of the
+modules that you use in your configuration.](conversation://Mara/hacker)
+
+NixOS modules are functions that take in the current state of the system and
+then return things to add to the state of the system. Here is a basic NixOS
+module that enables [nginx](https://nginx.org/):
+
+```nix
+{ config, pkgs, lib, ... }:
+
+{
+ config = {
+ services.nginx.enable = true;
+ };
+}
+```
+
+This function takes in the state of the world and returns additions to the state
+of the world. This will use the nginx module that ships with NixOS to give you a
+basic nginx setup that has the upstream default configuration in it.
+
+NixOS has a way to run other instances of NixOS with [NixOS
+containers](https://nixos.org/manual/nixos/stable/index.html#ch-containers). We
+can use them to test our NixOS module as we write it.
+
+[This probably won't work on a non-NixOS machine. You will need to
+install NixOS in order to test this. For an easy way to do this, see nixos-infect, a script you can
+put into a cloudconfig when spinning up a new server. You can also install
+NixOS manually in a VM, but for now it may be better to use a cloud server
+as the path of least resistance. Installing NixOS with a flake will be a part of
+a future article in this series.](conversation://Mara/hacker)
+
+In Nix you can merge two attribute sets using the `//` operator. This allows you
+to add two attribute sets into one larger one, such as like this:
+
+```
+nix-repl> { foo = 1; } // { bar = 2; }
+{ bar = 2; foo = 1; }
+```
+
+
+Important pro tip: the merge operator is NOT recursive. If you try to do
+something like:
+
+```
+nix-repl> foo = { bar = { baz = "foo"; }; }
+nix-repl> (foo // { bar = { spam = "eggs"; }; }).bar
+```
+
+You will get:
+
+```
+{ spam = "eggs"; }
+```
+
+And not:
+
+```
+{ baz = "foo"; spam = "eggs"; }
+```
+
+This is because the `//` operator prefers things in the right hand side over the
+left hand side if both conflict. To recursively merge two attribute sets (using
+all elements from both sides), use
+[lib.recursiveUpdate](https://nixos.org/manual/nixpkgs/stable/#function-library-lib.attrsets.recursiveUpdate):
+
+```
+nix-repl> (pkgs.lib.recursiveUpdate foo bar).bar
+{ baz = "foo"; spam = "eggs"; }
+```
+
+
+
+We will use this to add the container configuration to the flake at the end of
+the flake.nix file. We need to do this because the upper part of the flake with
+the `forAllSystems` call will generate a bunch of system-specific attributes for
+each system we support. NixOS configurations don't support this level of
+granularity.
+
+At the end of your flake.nix (just before the final closing `}`), there should
+be a line that looks like this:
+
+```nix
+ });
+```
+
+This is what terminates the `outputs` declaration from all the way at the top.
+In order to add the container configuration, you should change this to look like
+this:
+
+```nix
+ }) // {
+
+ };
+```
+
+Then we can add the container configuration to the flake:
+
+```nix
+}) // {
+ nixosConfigurations.container = nixpkgs.lib.nixosSystem {
+ system = "x86_64-linux";
+ modules = [
+ ({pkgs, ...}: {
+ # Only allow this to boot as a container
+ boot.isContainer = true;
+ networking.hostName = "gohello";
+
+ # Allow nginx through the firewall
+ networking.firewall.allowedTCPPorts = [ 80 ];
+
+ services.nginx.enable = true;
+ })
+ ];
+ };
+};
+```
+
+This will create a container (with the hostname "gohello") that starts nginx and
+allows traffic to go to nginx on TCP port 80. You can start up the container
+with the `nixos-container` command:
+
+```console
+$ sudo nixos-container create gohello --flake .#container
+host IP is 10.233.1.1, container IP is 10.233.1.2
+```
+
+Then you can start the container with this command:
+
+```console
+$ sudo nixos-container start gohello
+```
+
+And then we can try to connect to nginx to see if it's working:
+
+```console
+$ curl http://10.233.1.2
+
+
+
+Welcome to nginx!
+
+
+
+Welcome to nginx!
+If you see this page, the nginx web server is successfully installed and
+working. Further configuration is required.
+
+For online documentation and support please refer to
+nginx.org.
+Commercial support is available at
+nginx.com.
+
+Thank you for using nginx.
+
+
+```
+
+We have nginx!
+
+Now that we have our container to test with, let's write the configuration for
+the service. At a basic level we need the following things:
+
+- A systemd unit for orchestrating the HTTP server process
+- nginx configuration to reverse proxy to that HTTP server
+
+Above the container definition, add this basic NixOS module template:
+
+```nix
+nixosModule = { config, lib, pkgs, ... }:
+ with lib;
+ let cfg = config.xeserv.services.gohello;
+ in {
+ options.xeserv.services.gohello = {
+ enable = mkEnableOption "Enables the gohello HTTP service";
+ };
+
+ config = mkIf cfg.enable {
+ };
+ };
+```
+
+This will create a NixOS module that will only be enabled when the configuration
+setting `xeserv.services.gohello.enable` is set to `true`. Everything else we do
+here will build on this.
+
+[You can and probably do want to change the namespace `xeserv` here, it is a
+placeholder that is not likely to conflict with anything
+else.](conversation://Mara/happy)
+
+Create a basic systemd service with this template:
+
+```nix
+config = mkIf cfg.enable {
+ systemd.services."xeserv.gohello" = {
+ wantedBy = [ "multi-user.target" ];
+
+ serviceConfig = let pkg = self.packages.${system}.default;
+ in {
+ Restart = "on-failure";
+ ExecStart = "${pkg}/bin/web-server";
+ DynamicUser = "yes";
+ RuntimeDirectory = "xeserv.gohello";
+ RuntimeDirectoryMode = "0755";
+ StateDirectory = "xeserv.gohello";
+ StateDirectoryMode = "0700";
+ CacheDirectory = "xeserv.gohello";
+ CacheDirectoryMode = "0750";
+ };
+ };
+};
+```
+
+
+NOTE: If you have been following along since before this article was published,
+you will want to be sure to do the following things to your copy of gohello:
+
+* Move the definition of `defaultPackage` into the `packages` attribute set with
+ the name `default`
+* Update `defaultApp` and the other entries to point to
+ `self.packages.${system}.default` instead of `self.defaultPackage.${system}`
+
+We have updated previous articles and the template accordingly. Annoyingly it
+seems that this change is new enough that it isn't totally documented on the
+NixOS wiki. We are working on fixing this.
+
+
+
+This will do the following things:
+
+- Start the service on boot (`multi-user.target` fires once the system is "fully
+ booted" and the network is active)
+- Automatically restarts the service when it crashes
+- Starts our `web-server` binary when running the service
+- Creates a random, unique user account for the service (see
+ [here](http://0pointer.net/blog/dynamic-users-with-systemd.html) for more
+ information on how/why this works)
+- Creates temporary, home and cache directories for the service, makes sure that
+ random user has permission to use them (with the specified directory modes
+ too)
+- Enables the service automatically
+
+Then you need to add the nginx configuration. We want this application to have
+its own virtual host, so we will need to add that as a configuration option
+under the `enable` option:
+
+```nix
+domain = mkOption rec {
+ type = types.str;
+ default = "gohello.local.cetacean.club";
+ example = default;
+ description = "The domain name for gohello";
+};
+```
+
+[Pro tip: `anything.local.cetacean.club` points to `127.0.0.1`. You can use this
+when testing things.](conversation://Mara/happy)
+
+And then we can add the nginx configuration under the systemd service definition:
+
+```nix
+services.nginx.virtualHosts.${cfg.domain} = {
+ locations."/" = { proxyPass = "http://127.0.0.1:3031"; };
+};
+```
+
+Your module should look like this:
+
+```nix
+nixosModule = { config, lib, pkgs, ... }:
+ with lib;
+ let cfg = config.xeserv.services.gohello;
+ in {
+ options.xeserv.services.gohello = {
+ enable = mkEnableOption "Enables the gohello HTTP service";
+
+ domain = mkOption rec {
+ type = types.str;
+ default = "gohello.local.cetacean.club";
+ example = default;
+ description = "The domain name for gohello";
+ };
+ };
+
+ config = mkIf cfg.enable {
+ systemd.services."xeserv.gohello" = {
+ wantedBy = [ "multi-user.target" ];
+
+ serviceConfig = let pkg = self.packages.${pkgs.system}.default;
+ in {
+ Restart = "on-failure";
+ ExecStart = "${pkg}/bin/web-server";
+ DynamicUser = "yes";
+ RuntimeDirectory = "xeserv.gohello";
+ RuntimeDirectoryMode = "0755";
+ StateDirectory = "xeserv.gohello";
+ StateDirectoryMode = "0700";
+ CacheDirectory = "xeserv.gohello";
+ CacheDirectoryMode = "0750";
+ };
+ };
+
+ services.nginx.virtualHosts.${cfg.domain} = {
+ locations."/" = { proxyPass = "http://127.0.0.1:3031"; };
+ };
+ };
+ };
+```
+
+[The service name is overly defensive. It's intended to avoid conflicting with
+any other unit on the system named `gohello.service`. Feel free to remove this
+part, it is really just defensive devops by design to avoid name
+conflicts.](conversation://Mara/hacker)
+
+Then you can add it to the container by importing our new module in its
+configuration and activating the gohello service:
+
+```nix
+nixosConfigurations.container = nixpkgs.lib.nixosSystem {
+ system = "x86_64-linux";
+ modules = [
+ self.nixosModule
+ ({ pkgs, ... }: {
+ # Only allow this to boot as a container
+ boot.isContainer = true;
+
+ # Allow nginx through the firewall
+ networking.firewall.allowedTCPPorts = [ 80 ];
+
+ services.nginx.enable = true;
+
+ xeserv.services.gohello.enable = true;
+ })
+ ];
+};
+```
+
+Then you can update the container's configuration with this command:
+
+```console
+$ sudo nixos-container update gohello --flake .#container
+reloading container...
+```
+
+And finally make a request to the gohello service running in that container:
+
+```console
+$ curl http://10.233.1.2 -H "Host: gohello.local.cetacean.club"
+hello world :)
+```
+
+
+Exercises for the reader:
+
+Try adding a [nixos
+option](https://nixos.org/manual/nixos/stable/index.html#sec-writing-modules)
+that correlates to the `--bind` flag that `gohello` uses as the TCP
+address to serve HTTP from. You will want to have the type be
+`types.port`. If you are stuck, see
+[here](https://github.com/Xe/nixos-configs/tree/master/common/services) for inspiration.
+
+Also try adding `AmbientCapabilities = "CAP_NET_BIND_SERVICE"` and
+`CapabilityBoundingSet = "CAP_NET_BIND_SERVICE"` to your `serviceConfig` and
+bind `gohello` to port 80 without nginx involved at all.
+
+
+
+You can delete this container with `sudo nixos-container destroy gohello` when
+you are done with it.
+
+These are the basics on how to use NixOS modules. Everything else you can do
+with them builds off of these fundamental ideas. Modules are templates that
+coordinate packages and configuration into your desired system state. Containers
+can let you test out modules without having to add them to your currently
+running system. Modules declare options and emit configuration based on those
+options.
+
+You can also consume NixOS modules from flakes using the input system, however I
+will go into more details about this at a later date. If you want more examples
+of NixOS modules, I would suggest checking out my
+[nixos-configs](https://github.com/Xe/nixos-configs) repository. I have nearly
+everything neatly modularized and configurable. If you see anything in there
+that is confusing to you, please [reach out](/contact) and ask. I am happy to
+answer your questions and your feedback will help me write future posts in this
+series.
+
+I also have my "next generation" flakes-based configuration experiments
+[here](https://tulpa.dev/cadey/nixos-configs) if you want to read through those.
+I have still been porting over things piecemeal, so it is not a complete replica
+of my existing configuration.
+
+Next time I will cover how to install NixOS to a server and deploy system
+configurations using [deploy-rs](https://github.com/serokell/deploy-rs). This
+will allow you to have your workstation build configuration for your servers and
+push out all the changes from there.
+
+---
+
+Many thanks to Open Skies for being my fearless editor that helps make these
+things shine.
+
+In part of this post I use my new Xeact-powered HTML component for some of the
+conversation fragments, but the sizing was off on my iPhone when I tested it. If
+you know what I am doing wrong, please [get in touch](/contact).