summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCara Salter <cara@devcara.com>2022-04-06 16:57:28 -0400
committerCara Salter <cara@devcara.com>2022-04-06 16:57:28 -0400
commit716f68afd8fd3db5c0285939840a3adee04dc0dc (patch)
treedb62d76208fd5106ff5f62b7c8fb1ec847420c79
parentbd8f4fb2893172c985ebdad79ab9a664982ea7bc (diff)
downloadsite-716f68afd8fd3db5c0285939840a3adee04dc0dc.tar.gz
site-716f68afd8fd3db5c0285939840a3adee04dc0dc.zip
nixos deployments 1 post
-rw-r--r--blog/nixos-deployments.md301
-rw-r--r--flake.nix3
-rw-r--r--templates/footer.rs.html2
3 files changed, 303 insertions, 3 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
diff --git a/flake.nix b/flake.nix
index b197bb8..2a83e6c 100644
--- a/flake.nix
+++ b/flake.nix
@@ -34,8 +34,7 @@
git
];
overrideMain = attrs: {
- preBuild = ''
- ls -alR src
+ preBuild = ''
cp -r templates /build/dummy-src/
cp -r statics /build/dummy-src/
'';
diff --git a/templates/footer.rs.html b/templates/footer.rs.html
index f516aed..efc1392 100644
--- a/templates/footer.rs.html
+++ b/templates/footer.rs.html
@@ -5,7 +5,7 @@
href="https://github.com/Xe/site">Xe/site</a>
under the Zlib license</blockquote>
<p>This server runs version @env!("GIT_SHA"), located on my <a
- href='https://git.carathe.dev/muirrum/site/commit/@env!("GIT_SHA")'>Gitea</a>
+ href='https://git.carathe.dev/muirrum/site'>Gitea</a>
</footer>
</div>
</body>