diff --git a/blog/paranoid-nixos-aws-2021-08-11.markdown b/blog/paranoid-nixos-aws-2021-08-11.markdown
new file mode 100644
index 0000000..0059844
--- /dev/null
+++ b/blog/paranoid-nixos-aws-2021-08-11.markdown
@@ -0,0 +1,1292 @@
+---
+title: Paranoid NixOS on AWS
+date: 2021-08-11
+author: Heartmender
+series: nixos
+tags:
+ - paranix
+ - aws
+ - r13y
+---
+
+In [the last post](https://christine.website/blog/paranoid-nixos-2021-07-18) we
+covered a lot of the base groundwork involved in making a paranoid NixOS setup.
+Today we're gonna throw this into prod by making a base NixOS image with it.
+
+[Normally I don't suggest people throw these things into production directly, if
+only to have some kind of barrier between you and your money generator; however
+today is different. It's probably not completely unsafe to put this in
+production, but I really would suggest reading and understanding this article
+before doing so.](conversation://Cadey/coffee)
+
+At a high level we are going to do the following:
+
+- Pin production OS versions using [niv](https://github.com/nmattia/niv)
+- Create a script to automatically generate a production-ready NixOS image that
+ you can import into The Cloud
+- Manage all this using your favorite buzzwords (Terraform,
+ Infrastructure-as-Code)
+- Install an nginx server reverse proxying to the [Printer facts
+ service](https://printerfacts.cetacean.club/)
+
+## What is an Image?
+
+Before we yolo this all into prod, let's cover what we're actually doing.
+There are a lot of conflicting buzzwords here, so I'm going to go out of my way
+to attempt to simplify them down so that we use my arbitrary definitions of
+buzzwords instead of what other people will imply they mean. You're reading my
+blog, you get my buzzwords; it's as simple as that.
+
+In this post we are going to create a base system that you can build your
+production systems on top of. This base system will be crystallized into an
+_image_ that AWS will use as the initial starting place for servers.
+
+[So you create the system definition for your base system, then turn that into
+an image and put that image into AWS?](conversation://Mara/hmm)
+
+[Yep! The exact steps are a little more complicated but at a high level that's
+what we're doing.](conversation://Cadey/enby)
+
+## Base Setup
+
+I'm going to be publishing my work for this post
+[here](https://tulpa.dev/cadey/paranix-configs), but you can follow along in
+this post to understand the individual steps here.
+
+First, let's set up the environment with
+[lorri](https://github.com/nix-community/lorri) and
+[niv](https://github.com/nmattia/niv). Lorri will handle creating a cached
+nix-shell environment for us to run things in and niv will handle pinning NixOS
+to an exact version so you can get a more reproducible production environment.
+
+Set up lorri:
+
+```console
+$ lorri init
+Aug 11 09:41:50.966 INFO wrote file, path: ./shell.nix
+Aug 11 09:41:50.966 INFO wrote file, path: ./.envrc
+Aug 11 09:41:50.966 INFO done
+direnv: error /home/cadey/code/cadey/paranix-configs/.envrc is blocked. Run `direnv allow` to approve its content
+$ direnv allow
+direnv: loading ~/code/cadey/paranix-configs/.envrc
+Aug 11 09:41:54.581 INFO lorri has not completed an evaluation for this project yet, nix_file: /home/cadey/code/cadey/paranix-configs/shell.nix
+direnv: export +IN_NIX_SHELL
+```
+
+[Why are you putting the `$` before every command in these examples? It looks
+extraneous to me.](conversation://Mara/hacker)
+
+[The `$` is there for two main reasons. First, it allows there to be a clear
+delineation between the commands being typed and their output. Secondly it makes
+it slightly harder to blindly copy this into your shell without either editing
+the `$` out or selecting around it. My hope is that this will make you read the
+command and carefully consider whether or not you actually want to run
+it.](conversation://Cadey/enby)
+
+Set up niv:
+
+```console
+$ niv init
+Initializing
+ Creating nix/sources.nix
+ Creating nix/sources.json
+ Importing 'niv' ...
+ Adding package niv
+ Writing new sources file
+ Done: Adding package niv
+ Importing 'nixpkgs' ...
+ Adding package nixpkgs
+ Writing new sources file
+ Done: Adding package nixpkgs
+Done: Initializing
+```
+
+[If you don't already have niv in your environment, you can hack around that by
+running all the niv commands before you set up `shell.nix` like this:
$ nix-shell -p niv --run 'niv blah'
](conversation://Mara/hacker)
+
+And finally pin nixpkgs to a specific version of NixOS.
+
+[At the time of writing this article, NixOS 21.05 is the stable release, so that
+is what is used here.](conversation://Mara/hacker)
+
+```console
+$ niv update nixpkgs -b nixos-21.05
+Update nixpkgs
+Done: Update nixpkgs
+$
+```
+
+This will become the foundation of our NixOS systems and production images.
+
+You should then set up your `shell.nix` to look like this:
+
+```nix
+let
+ sources = import ./nix/sources.nix;
+ pkgs = import ./sources.nixpkgs { };
+in pkgs.mkShell {
+ buildInputs = with pkgs; [
+ niv
+ terraform
+
+ bashInteractive
+ ];
+};
+```
+
+### Set Up Unix Accounts
+
+[This step can be omitted if you are grafting this into an existing NixOS
+configs repository, however it would be good to read through this to understand
+the directory layout at play here.](conversation://Mara/hacker)
+
+It's probably important to be able to have access to production machines. Let's
+create a NixOS module that will allow you to SSH into the machine. In your
+paranix-configs folder, run this command to make a `common` config directory:
+
+```console
+$ mkdir common
+$ cd common
+```
+
+Now in that common directory, open `default.nix` in ~~emacs~~ your favorite text
+editor and copy in this skeleton:
+
+```nix
+# common/default.nix
+
+{ config, lib, pkgs, ... }:
+
+{
+ imports = [ ./users.nix ];
+
+ nix.autoOptimiseStore = true;
+
+ users.users.root.openssh.authorizedKeys.keys = [ "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPg9gYKVglnO2HQodSJt4z4mNrUSUiyJQ7b+J798bwD9" ];
+
+ services.tailscale.enable = true;
+
+ # Tell the firewall to implicitly trust packets routed over Tailscale:
+ networking.firewall.trustedInterfaces = [ "tailscale0" ];
+
+ security.auditd.enable = true;
+ security.audit.enable = true;
+ security.audit.rules = [
+ "-a exit,always -F arch=b64 -S execve"
+ ];
+
+ security.sudo.execWheelOnly = true;
+ environment.defaultPackages = lib.mkForce [];
+
+ services.openssh = {
+ passwordAuthentication = false;
+ allowSFTP = false; # Don't set this if you need sftp
+ challengeResponseAuthentication = false;
+ extraConfig = ''
+ AllowTcpForwarding yes
+ X11Forwarding no
+ AllowAgentForwarding no
+ AllowStreamLocalForwarding no
+ AuthenticationMethods publickey
+ '';
+ };
+
+ # PCI compliance
+ environment.systemPackages = with pkgs; [ clamav ];
+}
+```
+
+[Astute readers will notice that this is less paranoid than the last post. This
+was pared down after private feedback.](conversation://Mara/hacker)
+
+This will create `common` as a folder that can be imported as a NixOS module
+with some basic settings and then tells NixOS to try importing `users.nix` as a
+module. This module doesn't exist yet, so it will fail when we try to import it.
+Let's fix that by making `users.nix`:
+
+```nix
+# common/users.nix
+
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ # These options will be used for user account defaults in
+ # the `mkUser` function.
+ xeserv.users = {
+ groups = mkOption {
+ type = types.listOf types.str;
+ default = [ "wheel" ];
+ example = ''[ "wheel" "libvirtd" "docker" ]'';
+ description =
+ "The Unix groups that Xeserv staff users should be assigned to";
+ };
+
+ shell = mkOption {
+ type = types.package;
+ default = pkgs.bashInteractive;
+ example = "pkgs.powershell";
+ description =
+ "The default shell that Xeserv staff users will be given by default.";
+ };
+ };
+
+ cfg = config.xeserv.users;
+
+ mkUser = { keys, shell ? cfg.shell, extraGroups ? cfg.groups, ... }: {
+ isNormalUser = true;
+ inherit extraGroups shell;
+ openssh.authorizedKeys = {
+ inherit keys;
+ };
+ };
+in {
+ options.xeserv.users = xeserv.users;
+
+ config.users.users = {
+ cadey = mkUser {
+ keys = [ "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPg9gYKVglnO2HQodSJt4z4mNrUSUiyJQ7b+J798bwD9" ];
+ };
+ twi = mkUser {
+ keys = [ "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPYr9hiLtDHgd6lZDgQMkJzvYeAXmePOrgFaWHAjJvNU" ];
+ };
+ };
+}
+```
+
+[It's worth noting that `xeserv` in there can be anything you want. It's set to
+`xeserv` as we are imagining that this is for the production environment of a
+company named Xeserv.](conversation://Mara/hacker)
+
+### Paranoid Settings
+
+Next we're going to set up the paranoid settings from the last post into a
+module named `paranoid.nix`. First we'll need to grab
+[impermanence](https://github.com/nix-community/impermanence) into our niv
+manifest like this:
+
+```console
+$ niv add nix-community/impermanence
+Adding package impermanence
+ Writing new sources file
+Done: Adding package impermanence
+```
+
+Then open `common/default.nix` and change this line:
+
+```nix
+imports = [ ./users.nix ];
+```
+
+To something like this:
+
+```nix
+imports = [ ./paranoid.nix ./users.nix ];
+```
+
+Then open `./paranoid.nix` in a text editor and paste in the following:
+
+```nix
+# common/paranoid.nix
+
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+let
+ sources = import ../nix/sources.nix;
+ impermanence = sources.impermanence;
+ cfg = config.xeserv.paranoid;
+
+ ifNoexec = if cfg.noexec then [ "noexec" ] else [ ];
+in {
+ imports = [ "${impermanence}/nixos.nix" ];
+
+ options.xeserv.paranoid = {
+ enable = mkEnableOption "enables ephemeral filesystems and limited persistence";
+ noexec = mkEnableOption "enables every mount on the system save /nix being marked as noexec (potentially dangerous at a social level)";
+ };
+
+ config = mkIf cfg.enable {
+ fileSystems."/" = mkForce {
+ device = "none";
+ fsType = "tmpfs";
+ options = [ "defaults" "size=2G" "mode=755" ] ++ ifNoexec;
+ };
+
+ fileSystems."/etc/nixos".options = ifNoexec;
+ fileSystems."/srv".options = ifNoexec;
+ fileSystems."/var/lib".options = ifNoexec;
+ fileSystems."/var/log".options = ifNoexec;
+
+ fileSystems."/boot" = {
+ device = "/dev/disk/by-label/boot";
+ fsType = "vfat";
+ };
+
+ fileSystems."/nix" = {
+ device = "/dev/disk/by-label/nix";
+ autoResize = true;
+ fsType = "ext4";
+ };
+
+ boot.cleanTmpDir = true;
+
+ environment.persistence."/nix/persist" = {
+ directories = [
+ "/etc/nixos" # nixos system config files, can be considered optional
+ "/srv" # service data
+ "/var/lib" # system service persistent data
+ "/var/log" # the place that journald dumps it logs to
+ ];
+ };
+
+ environment.etc."ssh/ssh_host_rsa_key".source =
+ "/nix/persist/etc/ssh/ssh_host_rsa_key";
+ environment.etc."ssh/ssh_host_rsa_key.pub".source =
+ "/nix/persist/etc/ssh/ssh_host_rsa_key.pub";
+ environment.etc."ssh/ssh_host_ed25519_key".source =
+ "/nix/persist/etc/ssh/ssh_host_ed25519_key";
+ environment.etc."ssh/ssh_host_ed25519_key.pub".source =
+ "/nix/persist/etc/ssh/ssh_host_ed25519_key.pub";
+ environment.etc."machine-id".source = "/nix/persist/etc/machine-id";
+ };
+}
+```
+
+This should give us the base that we need to build the system image for AWS.
+
+## Building The Image
+
+As I mentioned earlier we need to build a system image before we can build the
+image. NixOS normally hides a lot of this magic from you, but we're going to
+scrape away all that magic and do this by hand. In your `paranix-configs`
+folder, create a folder named `images`. This creatively named folder is where we
+will store our NixOS image generation scripts.
+
+Copy this code into `build.nix`. This will tell NixOS to create a new system
+closure with configuration in `images/configuration.nix`:
+
+```nix
+# images/build.nix
+
+let
+ sources = import ../nix/sources.nix;
+ pkgs = import sources.nixpkgs { };
+ sys = (import "${sources.nixpkgs}/nixos/lib/eval-config.nix" {
+ system = "x86_64-linux";
+ modules = [ ./configuration.nix ];
+ });
+in sys.config.system.build.toplevel
+```
+
+And in `images/configuration.nix` add this skeleton config:
+
+```nix
+# images/configuration.nix
+
+{ config, pkgs, lib, modulesPath, ... }:
+
+{
+ imports = [ ../common (modulesPath + "/virtualisation/amazon-image.nix") ];
+
+ xeserv.paranoid.enable = true;
+}
+```
+
+[You can adapt this to other clouds by changing what module is imported. See the
+list of available modules here.](conversation://Mara/hacker)
+
+Then you can kick off the build with `nix-build`:
+
+```console
+$ nix-build build.nix
+```
+
+It will take a moment to assemble everything together and when you are done you
+should have an entire functional system closure in `./result`:
+
+```console
+$ cat ./result/nixos-version
+21.05pre-git
+```
+
+[It has `pre-git` here because we're using a pinned commit of the `nixos-21.05`
+git branch. Release channels don't have that suffix there.](conversation://Mara/hacker)
+
+From here we need to put this base system closure into a disk image for AWS.
+This process is a bit more involved, but here are the high level things needed
+to make a disk image for NixOS (or any Linux system for that matter):
+
+- A virtual hard drive to install the OS to
+- A partition mapping on the virtual hard drive
+- Essential system files copied over
+- A boot configuation
+
+We can model this using a Nix function. This function would need to take in the
+system config, some metadata about the kind of image to make and then it would
+build the image and return the result. I've made this available
+[here](https://tulpa.dev/cadey/paranix-configs/src/branch/main/images/make-image.nix)
+so you can grab it into your config folder like this:
+
+```console
+$ wget -O make-image.nix https://tulpa.dev/cadey/paranix-configs/raw/branch/main/images/make-image.nix
+```
+
+Then we can edit `build.nix` to look like this:
+
+```nix
+# images/build.nix
+
+let
+ sources = import ../nix/sources.nix;
+ pkgs = import sources.nixpkgs { };
+ config = (import "${sources.nixpkgs}/nixos/lib/eval-config.nix" {
+ system = "x86_64-linux";
+ modules = [ ./configuration.nix ];
+ });
+
+in import ./make-image.nix {
+ inherit (config) config pkgs;
+ inherit (config.pkgs) lib;
+ format = "vpc"; # change this for other clouds
+}
+```
+
+Then you can build the AWS image with `nix-build`:
+
+```console
+$ nix-build build.nix
+```
+
+This will emit the AWS disk image in `./result`:
+
+```console
+$ ls ./result/
+nixos.vhd
+```
+
+[AWS uses Microsoft Virtual PC hard disk files as the preferred input for their
+vmimport service. This is probably a legacy thing.](conversation://Mara/hacker)
+
+## Terraforming
+
+[Terraform](https://www.terraform.io/) is not my favorite tool on the planet,
+however it is quite useful for beating AWS and other clouds into shape. We will
+be using Terraform to do the following:
+
+- Create an S3 bucket to use for storing Terraform states in The Cloud
+- Create an S3 bucket for the AMI base images
+- Create an IAM role for importing AMIs
+- Create an IAM role policy for allowing the AMI importer service to work
+- Uploading the image to S3
+- Import the image from S3 as an EBS snapshot
+- Create an AMI from that EBS snapshot
+- Create an example t2.micro virtual machine
+- Deploy an example service config for nginx that does nothing
+
+This sounds like a lot, but it's really not as much as it sounds. A lot of this
+is boilerplate. The cost associated with these steps should be minimal.
+
+In the root of your `paranix-configs` folder, make a folder called `terraform`,
+as this is where our terraform configuration will live:
+
+```console
+$ mkdir terraform
+$ cd terraform
+```
+
+Then you can proceed to the following steps.
+
+### S3 State Bucket
+
+In that folder, make a folder called `bootstrap`, this configuration will
+contain the base S3 bucket config for Terraform state:
+
+```console
+$ mkdir bootstrap
+$ cd bootstrap
+```
+
+Copy this terraform code into `main.tf`:
+
+```hcl
+# terraform/bootstrap/main.tf
+
+provider "aws" {
+ region = "us-east-1"
+}
+
+resource "aws_s3_bucket" "bucket" {
+ bucket = "xeserv-tf-state-paranix"
+ acl = "private"
+
+ tags = {
+ Name = "Terraform State"
+ }
+}
+```
+
+Then run `terraform init` to set up the terraform environment:
+
+```console
+$ terraform init
+```
+
+It will download the AWS provider and run a few tests on your config to make
+sure things are correct. Once this is done, you can run `terraform plan`:
+
+```console
+$ terraform plan
+Terraform used the selected providers to generate the following execution plan. Resource actions
+are indicated with the following symbols:
+ + create
+
+Terraform will perform the following actions:
+
+ # aws_s3_bucket.bucket will be created
+ + resource "aws_s3_bucket" "bucket" {
+ + acceleration_status = (known after apply)
+ + acl = "private"
+ + arn = (known after apply)
+ + bucket = "xeserv-tf-state-paranoid"
+ + bucket_domain_name = (known after apply)
+ + bucket_regional_domain_name = (known after apply)
+ + force_destroy = false
+ + hosted_zone_id = (known after apply)
+ + id = (known after apply)
+ + region = (known after apply)
+ + request_payer = (known after apply)
+ + tags = {
+ + "Name" = "Terraform State"
+ }
+ + tags_all = {
+ + "Name" = "Terraform State"
+ }
+ + website_domain = (known after apply)
+ + website_endpoint = (known after apply)
+
+ + versioning {
+ + enabled = (known after apply)
+ + mfa_delete = (known after apply)
+ }
+ }
+
+Plan: 1 to add, 0 to change, 0 to destroy.
+
+Note: You didn't use the -out option to save this plan, so Terraform can't guarantee to take
+exactly these actions if you run "terraform apply" now.
+```
+
+Terraform is very pedantic about what the state of the world is. In this case
+nothing in the associated state already exists, so it is saying that it needs to
+create the S3 bucket that we will use for our Terraform states in the future. We
+can apply this with `terraform apply`:
+
+```console
+$ terraform apply
+