blog: add third nix flakes post #2
|
@ -136,7 +136,10 @@ Then you can look at `flake.nix` to see what's up:
|
||||||
pkgs = nixpkgsFor.${system};
|
pkgs = nixpkgsFor.${system};
|
||||||
in
|
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";
|
pname = "go-hello";
|
||||||
inherit version;
|
inherit version;
|
||||||
# In 'nix develop', we don't need a copy of the source tree
|
# 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=";
|
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 }: {
|
outputs = { self, nixpkgs }: {
|
||||||
packages = { ... };
|
packages = { ... };
|
||||||
defaultPackage = { ... };
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
@ -462,7 +459,7 @@ nixosModules.bot = { config, lib, ... }: {
|
||||||
Group = "mara-bot";
|
Group = "mara-bot";
|
||||||
Restart = "always";
|
Restart = "always";
|
||||||
WorkingDirectory = "/var/lib/mara-bot";
|
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
|
however flakes have been fairly consistent for a few years
|
||||||
now.](conversation://Cadey/coffee)
|
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>
|
</div>
|
||||||
|
|
||||||
[What is a package? I've seen this term thrown around with phrases like "Nix is a
|
[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
|
```nix
|
||||||
# flake.nix
|
# flake.nix
|
||||||
|
|
||||||
# after defaultPackage
|
|
||||||
packages = {
|
packages = {
|
||||||
|
default = ...;
|
||||||
docker = let
|
docker = let
|
||||||
web = self.defaultPackage.${system};
|
web = self.packages.${system}.default;
|
||||||
in pkgs.dockerTools.buildLayeredImage {
|
in pkgs.dockerTools.buildLayeredImage {
|
||||||
name = web.pname;
|
name = web.pname;
|
||||||
tag = web.version;
|
tag = web.version;
|
||||||
|
@ -373,7 +377,7 @@ the systemd unit:
|
||||||
web-service = pkgs.substituteAll {
|
web-service = pkgs.substituteAll {
|
||||||
name = "web-server.service";
|
name = "web-server.service";
|
||||||
src = ./systemd/web-server.service.in;
|
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
|
```nix
|
||||||
portable = let
|
portable = let
|
||||||
web = self.defaultPackage.${system};
|
web = self.packages.${system}.default;
|
||||||
in pkgs.portableService {
|
in pkgs.portableService {
|
||||||
inherit (web) version;
|
inherit (web) version;
|
||||||
name = web.pname;
|
name = web.pname;
|
||||||
|
|
|
@ -0,0 +1,364 @@
|
||||||
|
---
|
||||||
|
title: "Nix Flakes: Exposing and using NixOS Modules"
|
||||||
|
date: 2022-03-31
|
||||||
|
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 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 the main building block of how NixOS servers are configured.
|
||||||
|
They are like lego blocks that help you build up a server from off the shelf
|
||||||
|
parts. A module describes a desired system state and they build off of eachother
|
||||||
|
in order to end up with a more elaborate result.
|
||||||
|
|
||||||
|
[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.](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; }
|
||||||
|
```
|
||||||
|
|
||||||
|
We will use this to add the container configuration to the flake at the end of
|
||||||
|
the flake.nix file. 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";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
[NOTE: You will want to be sure to do the following things to your copy of
|
||||||
|
gohello: <ul><li>Move the definition of `defaultPackage` into the `packages` attribute set with the name `default` </li><li>Update `defaultApp` and the other entries to point to `self.packages.${system}.default` instead of `self.defaultPackage.${system}`</li></ul> We have updated previous articles and the template
|
||||||
|
accordingly.](conversation://Mara/hacker)
|
||||||
|
|
||||||
|
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 user for the service
|
||||||
|
- 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"; };
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
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 :)
|
||||||
|
```
|
||||||
|
|
||||||
|
[As an exercise for the reader, try adding a <a
|
||||||
|
href="https://nixos.org/manual/nixos/stable/index.html#sec-writing-modules">nixos
|
||||||
|
option</a> 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`.](conversation://Mara/hacker)
|
||||||
|
|
||||||
|
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.
|
Loading…
Reference in New Issue