Starting with NixOps (and thus Nix and NixOS), part 2
In part 1, I showed how to write a very basic Nix expression to describe a machine to be deployed on Digital Ocean using NixOps, and the few commands necessary to deploy and destroy it.
I also showed that it was possible to SSH into it. Acutally this is not something you really want to do. Instead we will update the Nix expression to refine the setup of our droplet and let NixOps do the rest. (SSHing into a machine should only be used to investigate problems, not to change the state of a machine.)
In this post, I’ll show how to add a handful of things to our deployment.
.envrc
Having to specify the DIGITAL_OCEAN_AUTH_TOKEN
environment variable is tiresome (and possibly insecure since it is
visible is various places, e.g. the shell history), so I’m using a
.envrc
file:
$ echo 'export DIGITAL_OCEAN_AUTH_TOKEN=xxxx' > .envrc
$ direnv allow
In the reminder of the blog post, I will thus no longer prefix my commands with the Digital Ocean token.
Nginx
As a first modification to our deployment, let’s add Nginx. Since Nginx is already part of NixOS, we don’t have much to do:
- add a firewall rule to expose the port 80,
- enable Nginx.
This translates to the two following lines (to be added in the
machine-1
description):
networking.firewall.allowedTCPPorts = [ 80 ];
services.nginx.enable = true;
After deployment, you should be able to get an answer from your server:
$ nixops deploy -d do-rem
$ curl http://185.14.186.74
(The answer is a 404 since we need to further configure Nginx.)
As a reminder, in addition to retrieving the IP address from the
Digital Ocean web interface, you can also use
nixops info
.
Static site
In this section we prepare a simple one-file static site. We turn it into a Nix expression that we use in our deployment.
We create a directory (this could be a complete Git repository) to
contain the site and associated scripts (e.g. a static site generator
that would generate a _site
directory):
$ mkdir -p static_site/_site
$ echo "Hello." > static_site/_site/index.html
We add a default.nix
file to build the site:
with import <nixpkgs> {};
{
static_site = stdenv.mkDerivation {
name = "static_site";
src = ./.;
installPhase = ''
mkdir -p "$out/"
cp -a ./_site "$out/"
'';
};
}
The default.nix
file uses the standard Nix machinery,
nothing specific to NixOS. All it does is copying the _site
directory. In a more realistic setup, it would probably first build
it.
Now to use that package in our deployment, we:
- import it
- use it in the Nginx configuration
At the top of our do.nix
file we add
let
static_site = (import ./static_site).static_site;
in
We modify the Nginx part with:
services.nginx = {
enable = true;
virtualHosts = {
"example.com" = {
root = "${static_site}/_site";
};
};
};
After deploying again:
$ curl http://185.14.186.74
Hello.
Users
Adding users look like this:
users.mutableUsers = false;
users.extraUsers.toto = {
uid = 1000;
isNormalUser = true;
home = "/home/toto";
description = "The Toto User";
extraGroups = [ "wheel" ];
openssh.authorizedKeys.keys = [ "ssh-rsa xxxx toto@somewhere" ];
};
Stating mutableUsers = false
basically means that
existing users and passwords are configured by the deployment instead of
by login into the machine then changing things.
Now that we hase a user, we can provision some data directory:
system.activationScripts.toto =
''
echo Creating toto directories...
mkdir -m 0755 -p /home/toto/toto
chown toto:users /home/toto/toto
'';
We can confirm all is well:
$ nixops ssh -d do-rem machine-1
[root@machine-1:~]# su toto
[toto@machine-1:/root]$ cd
[toto@machine-1:~]$ ls
toto
Cron
We the above user and directory in place, we add a cron job to fill that directory:
services.cron = {
enable = true;
systemCronJobs = [
"0 5 * * * toto cd /home/toto/toto && date > date.log"
];
};
Packages
If you need a pakcage that is not automatically installed (i.e. it is
not yet a dependency of your deployment), you can specify it by itself.
Here we add wget
:
environment.systemPackages = [
pkgs.wget
];
Imports
Instead of having everything in the same Nix expression (beside the
static site), it is possible to use the imports
feature of
NixOS.
We remove the extraUsers
,activationScripts
and systemCronJobs
parts and move them to a new file,
toto.nix
:
{ config, pkgs, ... }: {
users.extraUsers.toto = {
uid = 1000;
isNormalUser = true;
home = "/home/toto";
description = "The Toto User";
extraGroups = [ "wheel" ];
openssh.authorizedKeys.keys = [ "ssh-rsa xxxx toto@somewhere" ];
};
system.activationScripts.toto =
''
echo Creating toto directories...
mkdir -m 0755 -p /home/toto/toto
chown toto:users /home/toto/toto
'';
services.cron.systemCronJobs = [
"35 * * * * toto cd /home/toto/toto && echo Hello > date.log"
];
}
In their place, we simply import the new file:
imports = [ ./toto.nix ];
NixOS will merge similar records to create the deployment.