about summary refs log tree commit diff
path: root/users/aspen/terraform/nixosMachine.nix
{ depot, pkgs, lib, ... }:

# mostly stolen from espes

{ name
, instanceType
, configuration
, prefix ? "${name}_"
, region ? "us-east-2"
, rootVolumeSizeGb ? 50
, securityGroupId ? null
, extraIngressPorts ? [ ]
}:

let
  os = depot.ops.nixos.nixosFor ({ modulesPath, ... }: {
    imports = [
      (pkgs.path + "/nixos/modules/virtualisation/amazon-image.nix")
      configuration
    ];

    ec2.hvm = true;
    networking.hostName = name;
    # TODO: remove this once the terraform tls provider supports ed25519 keys
    # https://github.com/hashicorp/terraform-provider-tls/issues/26
    services.openssh.extraConfig = ''
      PubkeyAcceptedKeyTypes=+ssh-rsa
      PubkeyAcceptedAlgorithms=+ssh-rsa
    '';
  });

  targetUser = "root";

  ec2Amis = import "${pkgs.path}/nixos/modules/virtualisation/ec2-amis.nix";

  osRoot = os.config.system.build.toplevel;

  osRootPath = builtins.unsafeDiscardStringContext (toString osRoot.outPath);
  drvPath = builtins.unsafeDiscardStringContext (toString osRoot.drvPath);

  machineResource = "aws_instance.${prefix}machine";

  recursiveMerge = builtins.foldl' lib.recursiveUpdate { };

  securityGroupId' =
    if isNull securityGroupId
    then "\${aws_security_group.${prefix}group.id}"
    else securityGroupId;
in
recursiveMerge [
  (lib.optionalAttrs (isNull securityGroupId) {
    resource.aws_security_group."${prefix}group" = {
      provider = "aws.${region}";
      vpc_id = null;

      # terraform isn't good about knowing what other resources depend on
      # security groups
      lifecycle.create_before_destroy = true;
    };

    resource.aws_security_group_rule.all_egress = {
      provider = "aws.${region}";
      security_group_id = securityGroupId';
      type = "egress";
      protocol = "-1";
      from_port = 0;
      to_port = 0;
      cidr_blocks = [ "0.0.0.0/0" ];
      ipv6_cidr_blocks = [ "::/0" ];

      description = null;
      prefix_list_ids = null;
      self = null;
    };
  })
  rec {
    data.external.my_ip = {
      program = [
        (pkgs.writeShellScript "my_ip" ''
          ${pkgs.jq}/bin/jq \
            -n \
            --arg ip "$(curl ifconfig.me)" \
            '{"ip":$ip}'
        '')
      ];
    };

    resource.aws_security_group_rule.provision_ssh_access = {
      provider = "aws.${region}";
      security_group_id = securityGroupId';
      type = "ingress";
      protocol = "TCP";
      from_port = 22;
      to_port = 22;
      cidr_blocks = [ "\${data.external.my_ip.result.ip}/32" ];
      ipv6_cidr_blocks = [ ];
      description = null;
      prefix_list_ids = null;
      self = null;
    };

    resource.tls_private_key."${prefix}key" = {
      algorithm = "RSA";
    };

    resource.aws_key_pair."${prefix}generated_key" = {
      provider = "aws.${region}";
      key_name = "generated-key-\${sha256(tls_private_key.${prefix}key.public_key_openssh)}";
      public_key = "\${tls_private_key.${prefix}key.public_key_openssh}";
    };

    resource.aws_instance."${prefix}machine" = {
      provider = "aws.${region}";
      ami = ec2Amis."21.05"."${region}".hvm-ebs;
      instance_type = instanceType;
      vpc_security_group_ids = [ securityGroupId' ];
      key_name = "\${aws_key_pair.${prefix}generated_key.key_name}";
      root_block_device = {
        volume_size = rootVolumeSizeGb;
        tags.Name = name;
      };
      tags.Name = name;
    };

    resource.null_resource."${prefix}deploy_nixos" = {
      triggers = {
        # deploy if the machine is recreated
        machine_id = "\${${machineResource}.id}";

        # deploy on os changes
        os_drv = drvPath;
      };

      connection = {
        type = "ssh";
        host = "\${${machineResource}.public_ip}";
        user = targetUser;
        private_key = "\${tls_private_key.${prefix}key.private_key_pem}";
      };

      # do the actual deployment
      provisioner = [
        # wait till ssh is up
        { remote-exec.inline = [ "true" ]; }

        # copy the nixos closure
        {
          local-exec.command = ''
            export PATH="${pkgs.openssh}/bin:$PATH"

            scratch="$(mktemp -d)"
            trap 'rm -rf -- "$scratch"' EXIT

            # write out ssh key
            echo -n "''${tls_private_key.${prefix}key.private_key_pem}" > $scratch/id_rsa.pem
            chmod 0600 $scratch/id_rsa.pem

            export NIX_SSHOPTS="\
                -o StrictHostKeyChecking=no\
                -o UserKnownHostsFile=/dev/null\
                -o GlobalKnownHostsFile=/dev/null\
                -o IdentityFile=$scratch/id_rsa.pem"

            nix-build ${drvPath}
            nix-copy-closure \
              --to ${targetUser}@''${${machineResource}.public_ip} \
              ${osRootPath} \
              --gzip \
              --use-substitutes
          '';
        }

        # activate it
        {
          remote-exec.inline = [
            # semicolons mandatory
            ''
              set -e;
              nix-env --profile /nix/var/nix/profiles/system --set ${osRootPath};
              ${osRootPath}/bin/switch-to-configuration switch;
            ''
          ];
        }
      ];
    };
  }

  {
    resource.aws_security_group_rule = builtins.listToAttrs (map
      (port: {
        name = "ingress_${toString port}";
        value = {
          provider = "aws.${region}";
          security_group_id = securityGroupId';
          type = "ingress";
          protocol = "TCP";
          from_port = port;
          to_port = port;
          cidr_blocks = [ "0.0.0.0/0" ];
          ipv6_cidr_blocks = [ ];
          description = null;
          prefix_list_ids = null;
          self = null;
        };
      })
      extraIngressPorts);
  }
]