about summary refs log tree commit diff
path: root/users/wpcarro/terraform/default.nix
blob: b3c16144a2096b9bddc36ce9dcba31cc8c054195 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
{ 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_tags = ["${name}-firewall"];
    };
  
    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"
          ];
        }
      ];
    };
  }));
}