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

let
  inherit (builtins) concatLists concatStringsSep toJSON unsafeDiscardStringContext;
  inherit (depot.users) wpcarro;
  inherit (pkgs) writeText;

  images = import "${pkgs.path}/nixos/modules/virtualisation/gce-images.nix";
  nixosImage = images."20.09";
in
{
  googleCloudVM =
    { project
    , name
    , region
    , zone
    , configuration
    , extraConfig ? { }
    ,
    }:
    let
      inherit (configuration.users.users) root;
      inherit (configuration.networking) firewall;

      # Convert NixOS-style port numbers to Terraform-style.
      asStrings = xs: map toString xs;
      asRanges = xs: map (x: "${toString x.from}-${toString x.to}") xs;

      sshKeys = concatStringsSep "\n"
        (map (key: "root:${key}") root.openssh.authorizedKeys.keys);

      os = depot.ops.nixos.nixosFor (_: {
        imports = [
          "${pkgs.path}/nixos/modules/virtualisation/google-compute-image.nix"
          configuration
        ];

        networking.hostName = name;

        fileSystems."/nix" = {
          device = "/dev/disk/by-label/google-${name}-disk";
          fsType = "ext4";
        };
      });

      osRoot = os.config.system.build.toplevel;
      osPath = unsafeDiscardStringContext (toString osRoot.outPath);
      drvPath = unsafeDiscardStringContext (toString osRoot.drvPath);
    in
    writeText "terraform.tf.json" (toJSON (lib.recursiveUpdate extraConfig {
      provider.google = {
        inherit project region zone;
      };

      resource.google_compute_instance."${name}" = {
        inherit name zone;
        machine_type = "e2-standard-2";

        tags = [
          "http-server"
          "https-server"
          "${name}-firewall"
        ];

        boot_disk = {
          device_name = "boot";
          initialize_params = {
            size = 10;
            image = "projects/nixos-cloud/global/images/${nixosImage.name}";
          };
        };

        attached_disk = {
          source = "\${google_compute_disk.${name}.id}";
          device_name = "${name}-disk";
        };

        network_interface = {
          network = "default";
          subnetwork = "default";
          access_config = { };
        };

        # Copy root's SSH keys from the NixOS configuration and expose them to the
        # metadata server.
        metadata = {
          inherit sshKeys;
          ssh-keys = sshKeys;

          # NixOS's fetch-instance-ssh-keys.bash relies on these fields being
          # available on the metadata server.
          ssh_host_ed25519_key = "\${tls_private_key.${name}.private_key_pem}";
          ssh_host_ed25519_key_pub = "\${tls_private_key.${name}.public_key_pem}";

          # Even though we have SSH access, having oslogin can still be useful for
          # troubleshooting in the browser if for some reason SSH isn't working as
          # expected.
          enable-oslogin = "TRUE";
        };

        service_account.scopes = [ "cloud-platform" ];
      };

      resource.tls_private_key."${name}" = {
        algorithm = "ECDSA";
        ecdsa_curve = "P384";
      };

      resource.google_compute_firewall."${name}" = {
        name = "${name}-firewall";
        network = "default";

        # Read the firewall configuration from the NixOS configuration.
        allow = [
          {
            protocol = "tcp";
            ports = concatLists [
              (asStrings (firewall.allowedTCPPorts or [ ]))
              (asRanges (firewall.allowedTCPPortRanges or [ ]))
            ];
          }
          {
            protocol = "udp";
            ports = concatLists [
              (asStrings (firewall.allowedUDPPorts or [ ]))
              (asRanges (firewall.allowedUDPPortRanges or [ ]))
            ];
          }
        ];
        source_ranges = [ "0.0.0.0/0" ];
      };

      resource.google_compute_disk."${name}" = {
        inherit zone;
        name = "${name}-disk";
        size = 100;
      };

      resource.null_resource.deploy_nixos = {
        triggers = {
          # Redeploy when the NixOS configuration changes.
          os = "${osPath}";
          # Redeploy when a new machine is provisioned.
          machine_id = "\${google_compute_instance.${name}.id}";
        };

        connection = {
          host = "\${google_compute_instance.${name}.network_interface[0].access_config[0].nat_ip}";
        };

        provisioner = [
          { remote-exec.inline = [ "true" ]; }
          {
            local-exec.command = ''
              export PATH="${pkgs.openssh}/bin:$PATH"

              scratch="$(mktemp -d)"
              function cleanup() {
                rm -rf $scratch
              }
              trap cleanup EXIT

              # write out ssh key
              echo -n "''${tls_private_key.${name}.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 \
                root@''${google_compute_instance.${name}.network_interface[0].access_config[0].nat_ip} \
                ${osPath} --gzip --use-substitutes
            '';
          }
          {
            remote-exec.inline = [
              "nix-env --profile /nix/var/nix/profiles/system --set ${osPath}"
              "${osPath}/bin/switch-to-configuration switch"
            ];
          }
        ];
      };
    }));
}