forked from cadey/xesite
Nix flakes 3 (#453)
* blog: add third nix flakes post Signed-off-by: Xe <me@christine.website> * make nix flakes post 3 better, thanks open Signed-off-by: Xe Iaso <me@christine.website>
This commit is contained in:
parent
6b771b5503
commit
449ddabce1
|
@ -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";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
|
|
@ -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)
|
||||
|
||||
</div>
|
||||
|
||||
[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;
|
||||
|
|
|
@ -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 <a href="/blog/series/nix-flakes">the other articles in
|
||||
this series</a>, 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 <a
|
||||
href="https://github.com/elitak/nixos-infect">nixos-infect</a>, a script you can
|
||||
put into a cloudconfig when spinning up a new server. You can also <a
|
||||
href="https://nixos.org/manual/nixos/stable/index.html#sec-installation">install
|
||||
NixOS manually</a> 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; }
|
||||
```
|
||||
|
||||
<xeblog-conv name="Mara" mood="hacker">
|
||||
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"; }
|
||||
```
|
||||
|
||||
</xeblog-conv>
|
||||
|
||||
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
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Welcome to nginx!</title>
|
||||
<style>
|
||||
body {}
|
||||
width: 35em;
|
||||
margin: 0 auto;
|
||||
font-family: Tahoma, Verdana, Arial, sans-serif;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Welcome to nginx!</h1>
|
||||
<p>If you see this page, the nginx web server is successfully installed and
|
||||
working. Further configuration is required.</p>
|
||||
|
||||
<p>For online documentation and support please refer to
|
||||
<a href="http://nginx.org/">nginx.org</a>.<br/>
|
||||
Commercial support is available at
|
||||
<a href="http://nginx.com/">nginx.com</a>.</p>
|
||||
|
||||
<p><em>Thank you for using nginx.</em></p>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
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";
|
||||
};
|
||||
};
|
||||
};
|
||||
```
|
||||
|
||||
<xeblog-conv name="Mara" mood="hacker">
|
||||
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.
|
||||
|
||||
</xeblog-conv>
|
||||
|
||||
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 :)
|
||||
```
|
||||
|
||||
<xeblog-conv name="Mara" mood="hacker">
|
||||
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.
|
||||
|
||||
</xeblog-conv>
|
||||
|
||||
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).
|
Loading…
Reference in New Issue