diff options
author | Cara Salter <cara@devcara.com> | 2022-04-06 16:57:28 -0400 |
---|---|---|
committer | Cara Salter <cara@devcara.com> | 2022-04-06 16:57:28 -0400 |
commit | 716f68afd8fd3db5c0285939840a3adee04dc0dc (patch) | |
tree | db62d76208fd5106ff5f62b7c8fb1ec847420c79 /blog | |
parent | bd8f4fb2893172c985ebdad79ab9a664982ea7bc (diff) | |
download | site-716f68afd8fd3db5c0285939840a3adee04dc0dc.tar.gz site-716f68afd8fd3db5c0285939840a3adee04dc0dc.zip |
nixos deployments 1 post
Diffstat (limited to 'blog')
-rw-r--r-- | blog/nixos-deployments.md | 301 |
1 files changed, 301 insertions, 0 deletions
diff --git a/blog/nixos-deployments.md b/blog/nixos-deployments.md new file mode 100644 index 0000000..fbe8066 --- /dev/null +++ b/blog/nixos-deployments.md @@ -0,0 +1,301 @@ +--- +title: Deploying with NixOS - Site Edition +date: 2022-04-06 +--- + +Today, I managed to make this site deployable via NixOS, as well as simplifying +the whole way I manage my NixOS servers. In this post, I want to walk you +through what I did and how I did it. + +## Original flake + +My [original][origflake] NixOS flakes were... a mess, to put it lightly. They were an +amalgamation of other people's flakes, and it led to things like my note-taking +laptop (which should have xournalpp on it and not much else) somehow running +libvirtd and Postgresql. Since I first wrote them, I've wanted to modularize my +home-manager configuration and allow it to be managed outside of the system +configuration. + +## Modularizing + +I'm pretty sure that's not a word, but it is now. The first step was to break my +`home-manager` configuration out of my NixOS system configuration. This would +let me switch around my home without needing to use `sudo`, but more +importantly, it would let me include modules properly based on the system +hostname. + +To do this, I ~~stole~~ wrote a function based on one by my friend Ellie, `hmConfig`. +It's similar to `mkSystem` in that it generates a proper configuration, except +that this one is for the specific user. You can see my function +[here][hmConfig], but it's nothing too complex. It sets my home directory, +username, and home state version. It then imports a baseline `./home/home.nix`, +which sets up some other things like my shell. + +The big thing is one I copied from `mkSystem`, the `++ extraImports`. This is +used further down in the flake in `homeConfigurations`, where I define the +specific modules that should be included in this home-manager configuration. For +example, my laptop (cesium), needs things like my mail setup, TeX, my X11 +configuration, and mpd. The servers I run, however, don't need anything special. +This lets each system be narrowed down to exactly what it needs. + +## Deploy-RS + +Two days ago, I converted two servers (`kronos` and `magnesium`) to NixOS. I +quickly realized how much of a pain updating these was going to be, as they got +out of sync with my flake. I could've set up a crontab to automatically apply +the latest flake every so often, but that would be too simple (and result in too +much waiting). + +Instead, I found a project called [serokell/deploy-rs][deployrs]. This let me +make a two-line script in my flake repository: + +``` +#!/usr/bin/env sh + +set -e +nix run github:serokell/deploy-rs +``` + +Every time I make a change to my flake, I can run this script and have it +automatically conform every managed system. It sets up my services, and all the +stuff I expect from one of my servers. When more servers come into the NixOS +fold, I have to do a few things. + +First, I need to apply a basic flake. I tend to use the `kronos` host, since +that's got nothing special attached to it. Quickly rewriting the installed +`configuration.nix` to enable flakes, I can run something like: +``` +nixos-rebuild switch --flake "git+https://git.carathe.dev/muirrum/nix#kronos" +``` + +This will install the kronos flake (which has the unfortunate side effect of +making some interesting network decisions and setting the hostname to `kronos`). +In the future, I'll probably write a generic `server` configuration that doesn't +do anything special but enable SSH and create my user. + +After that, I need to add the server to my deployment configuration. Near the +bottom of my `flake.nix` is a [`deploy`][mydeploy] output, that currently has two nodes +(the two servers I'm managing this way). It sets up the user that should be used +to SSH in as, and the nodes. I can conform this to a different configuration at +this time. + +Now that this is done, I can move on to automatically configuring my services. + +## Service flakes + +I decided to start with my site, because it's fairly simple as a service. All it +needs to do is parse some markdown and serve it. Should be pretty simple, right? + +Haha. This took two to three days of working out bugs in my site flake. The +first thing I had to do was configure some options for it and create a system +module. In my `flake.nix`, I wrote the following: + +``` +nixosModules.site = { config, lib, ... }: { + options = { + cara.services.carasite.enable = lib.mkEnableOption "enable cara's site"; + cara.services.carasite.domain = lib.mkOption { + type = lib.types.str; + default = "devcara.com"; + }; + cara.services.carasite.port = lib.mkOption { + type = lib.types.port; + default = 3000; + }; + }; +}; +``` + +This defines a few options that I can use in my generated configuration. Whether +or not the site should be enabled (that's the `mkEnableOption` call), and the +domain and port to use for Nginx. + +I prefixed my options with "cara" just to avoid collisions with proper `nixpkgs` +modules (not that `carasite` is ever going to end up in `nixpkgs`, but you never +know). + +Next up was writing the "implementation", which takes the options and generates +the way that the system should look to make the service work. My basic one looks +like this: + +``` +nixosModules.site = { config, lib, ... }: { + ... + + config = lib.mkIf config.cara.services.carasite.enable { + users.groups.cara-site = { + ... + }; + users.users.cara-site = { + ... + }; + + systemd.services.cara-site = { + ... + }; + + networking.firewall.allowedTCPPorts = [ ... ]; + + services.nginx = { + ... + }; + }; +}; +``` + +I'll go into more details on what each of those sections do in a bit. Basically, +this takes the options I'm going to set in my per-host configuration and turn it +into a workable service deployment. + +### Implementation Details + +#### Users & Groups +Each of these attribute sets configures a different aspect of what makes this +site run. First up are the user and group management bits, they create a service +user and group that the site will run as. + +``` +users.groups.cara-site = { + members = [ "cara-site" ]; +} + +users.users.cara-site = { + createHome = true; + isSystemUser = true; + home = "/var/lib/cara-site"; + group = "cara-site"; +}; +``` +Pretty standard stuff, and described more thoroughly in the [NixOS manual][manual-users]. + +#### Systemd + +This is definitely the place where I had the most trouble. I was running into a +few errors related to the way I had set up my `serviceConfig`, but that was +fixed by properly setting the `WorkingDirectory`. + +``` +systemd.services.cara-site = { + wantedBy = [ "multi-user.target" ]; + + environment = { + PORT = "${toString (config.cara.services.carasite.port)}"; + }; + + serviceConfig = { + User = "cara-site"; + Group = "cara-site"; + Restart = "always"; + WorkingDirectory = "${defaultPackage}"; + ExecStart = "${defaultPackage}/bin/carasite"; + }; +}; +``` + +In the end, it's a pretty simple systemd service. It runs in the store directory +where the built site is kept, and runs as the user we configured just a moment +ago. + +Here we see one of the frustrations of the Nix options system. For some reason, +the `lib.types.port` type can't be automatically made into a string, as an +integer. Even though the whole purpose of ports is to be made into strings and +then put into configuration files somehow, you still need the whole `${toString(...)}` +crap. + +#### Nginx +Nginx was fairly simple. All it took was configuring the ACME settings somewhere +else in my system flake and opening the right firewall ports. + +``` +networking.firewall.allowedTCPPorts = [ 443 80 ]; + +services.nginx = { + enable = true; + recommendedProxySettings = true; + recommendedTlsSettings = true; + + virtualHosts."${config.cara.services.carasite.domain}" = { + forceSSL = true; + enableACME = true; + + locations."/" = { + proxyPass = "http://127.0.0.1:${toString (config.cara.services.carasite.port)}"; + }; + }; +}; +``` + +In the future, I should probably move the `127.0.0.1` to something that lets me +configure the bind host, but it's working for now. + +## Deploying + +Now that the flake is all done, it comes time to deploy it to a server. `kronos` +is my static site host, so that's where I'll be putting this. + +First, I added it to my system flake's `inputs`, like this: +``` +inputs = { + ... + + carasite = { + url = "git+https://git.carathe.dev/muirrum/site"; + inputs.nixpkgs.follows = "nixpkgs"; + }; +}; +``` + +This tells my flake to include `carasite` as a dependency and make its version +of `nixpkgs` follow the system flake's version. + +Next, I added it to my `mkSystem` function: +``` +mkSystem = conf: + nixpkgs.lib.nixosSystem rec { + modules = [ + ... + + carasite.nixosModules.${system}.site + ]; + ... + }; +``` + +Note that this won't enable it for all systems, it still needs to be enabled +with the `cara.services.carasite.enable` option we defined in the site flake. + +Then, I went into my `hosts/kronos/default.nix` file, and added the +configuration snippet: +``` +cara.services.carasite = { + enable = true; + + domain = "devcara.com"; + port = 3030; +}; +``` + +This tells the flake to enable the site only on the `kronos` server, and also +explicitly sets the defaults just in case. + +Now, the only thing left to do is run the deploy script and watch the magic +work! + +## Conclusion + +*Wow*. This method of deploying software is so much easier and more predictable +than any other I've tried. I used to have to manually SSH in and update the site +whenever something changed, and now I can do it from my laptop. + +The added convienence of being able to apply the same basic settings to all my +servers without needing to use something like ansible is also helpful. + +I think a few of my Discord bots are going to be moved over next. Those should +present some added challenges in that they all require PostgreSQL to function +properly. + +[origflake]: https://git.carathe.dev/muirrum/nix/src/tag/pre-modules-home +[hmConfig]: https://git.carathe.dev/muirrum/nix/src/branch/master/flake.nix#L27 +[deployrs]: https://github.com/serokell/deploy-rs +[mydeploy]: https://git.carathe.dev/muirrum/nix/src/branch/master/flake.nix#L95 +[manual-users]: https://nixos.org/manual/nixos/unstable/index.html#sec-user-management |