web/src/content/blog/01.md
2024-05-05 16:19:04 -04:00

4.9 KiB

Deploying an Astro project on NixOS

Nix is an incredible project and has completely change the way I think about configuring linux and macOS environments. Recently, I moved my personal server from Ubuntu to NixOS to match my desktop environment. (dotfiles here!). In doing so, I realized I needed to move this blog over, too. I could simply deploy a docker container like I did before, but I think it would be interesting and informative to try and build a NixOS module around it. Hopefully you find it useful :)

The flake.nix file.

Nix has a experimental feature called flakes, if you've been in the NixOS space long enough you've undoubtably heard of them. Flakes are a new way of writing your package configuration, you expose two objects, inputs and outputs. Your inputs would be things your application depends on, for example, nixpkgs (the package repository of Nix). Inputs can be a variety of different things, but typically are git repos. A flake.lock file is generated automatically so any consumers of your flake get the exact revisions of each input to guarantee reproducability. The outputs section of your flake can contain many things, from devShells to entire nixosConfigurations, but we are interested in packages and nixosModules today.

A example for a starter flake for a Astro project may look like this:

{
nputs = {
 nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
 systems.url = "github:nix-systems/default";
;

utputs = {
 systems,
 nixpkgs,
 ...
 @ inputs: let
 eachSystem = f:
   nixpkgs.lib.genAttrs (import systems) (
     system:
       f nixpkgs.legacyPackages.${system}
   );
n {
 devShells = eachSystem (pkgs: {
   default = pkgs.mkShell {
     buildInputs = [
       pkgs.nodejs

       pkgs.nodePackages.pnpm

       pkgs.nodePackages.typescript
       pkgs.nodePackages.typescript-language-server
       pkgs.nodePackages."@tailwindcss/language-server"
       pkgs.nodePackages."@astrojs/language-server"
     ];
   };
 });
;

You can see for my inputs I am taking in nixpkgs and nix-systems, which I am using to generate a devShell for every system architechture supported by NixOS. The devShells all import the following nix packages, nodejs, pnpm, typescript, typescript-language-server, tailwindcss-language-server, astrojs-lanaguage-server. Lets add add a package!

I use pnpm as my package manager of choice, and as such we have to add the pnpm2nix flake to our inputs, which we can do with the following line.

 pnpm2nix.url = "github:nzbr/pnpm2nix-nzbr";

Now, lets add the package spec:

...

n {
 # add packages :)
 packages = eachSystem (pkgs: {
   default = inputs.pnpm2nix.packages.${pkgs.system}.mkPnpmPackage {
     name = "zm-blog";
     src = ./.;
     packageJSON = ./package.json;
     pnpmLock = ./pnpm-lock.yaml;
   };
 });

 ...
;

Now, when we run nix build, everything works as expected, great! But how can we see the outputs of our build? If we run the following command:

󰘧 | nix eval --raw .#packages.x86_64-linux.default
/nix/store/ik5nmb60qrgib5knp4b538axwdxykc8z-zm-blog

Awesome, if we ls this path, we get exactly what we are expecting from the build output.

󰘧 | ls /nix/store/ik5nmb60qrgib5knp4b538axwdxykc8z-zm-blog                                                                     nix-shell-env
 _astro   fonts                    blog-placeholder-2.jpg   blog-placeholder-5.jpg      󰗀 rss.xml
 about    index.html               blog-placeholder-3.jpg   blog-placeholder-about.jpg  󰗀 sitemap-0.xml
 blog     blog-placeholder-1.jpg   blog-placeholder-4.jpg  󰕙 favicon.svg                 󰗀 sitemap-index.xml

At this point, we could add this repo as a input to the flake configuring our server, add a new virtualHost for the domain we want this to run on, point the root at this package and call it a day, but I want to take it one step further. I want to write a NixOS module to make the configuration server side even easier.

Add the following code to the outputs section of your flake.nix.

nixosModule = {
   config,
   lib,
   pkgs,
   ...
 }:
   with lib; let
     cfg = config.zmio.blog;
   in {
     options.zmio.blog = {
       enable = mkEnableOption "Enables the Blog Site";

       domain = mkOption rec {
         type = type.str;
         default = "zackmyers.io";
         example = default;
         description = "The domain name for the website";
       };

       ssl = mkOption rec {
         type = type.bool;
         default = true;
         example = default;
         description = "Whether to enable SSL on the domain or not";
       };
     };

     config = mkIf cfg.enable {
       services.nginx.virtualHosts.${cfg.domain} = {
         forceSSL = cfg.ssl;
         enableACME = cfg.ssl;
         root = "${packages.${pkgs.system}.default}";
       };
     };
   };