summary refs log tree commit diff
path: root/nixos/modules/virtualisation/digital-ocean-config.nix
diff options
context:
space:
mode:
Diffstat (limited to 'nixos/modules/virtualisation/digital-ocean-config.nix')
-rw-r--r--nixos/modules/virtualisation/digital-ocean-config.nix197
1 files changed, 197 insertions, 0 deletions
diff --git a/nixos/modules/virtualisation/digital-ocean-config.nix b/nixos/modules/virtualisation/digital-ocean-config.nix
new file mode 100644
index 00000000000..88cb0cd450e
--- /dev/null
+++ b/nixos/modules/virtualisation/digital-ocean-config.nix
@@ -0,0 +1,197 @@
+{ config, pkgs, lib, modulesPath, ... }:
+with lib;
+{
+  imports = [
+    (modulesPath + "/profiles/qemu-guest.nix")
+    (modulesPath + "/virtualisation/digital-ocean-init.nix")
+  ];
+  options.virtualisation.digitalOcean = with types; {
+    setRootPassword = mkOption {
+      type = bool;
+      default = false;
+      example = true;
+      description = "Whether to set the root password from the Digital Ocean metadata";
+    };
+    setSshKeys = mkOption {
+      type = bool;
+      default = true;
+      example = true;
+      description = "Whether to fetch ssh keys from Digital Ocean";
+    };
+    seedEntropy = mkOption {
+      type = bool;
+      default = true;
+      example = true;
+      description = "Whether to run the kernel RNG entropy seeding script from the Digital Ocean vendor data";
+    };
+  };
+  config =
+    let
+      cfg = config.virtualisation.digitalOcean;
+      hostName = config.networking.hostName;
+      doMetadataFile = "/run/do-metadata/v1.json";
+    in mkMerge [{
+      fileSystems."/" = {
+        device = "/dev/disk/by-label/nixos";
+        autoResize = true;
+        fsType = "ext4";
+      };
+      boot = {
+        growPartition = true;
+        kernelParams = [ "console=ttyS0" "panic=1" "boot.panic_on_fail" ];
+        initrd.kernelModules = [ "virtio_scsi" ];
+        kernelModules = [ "virtio_pci" "virtio_net" ];
+        loader = {
+          grub.device = "/dev/vda";
+          timeout = 0;
+          grub.configurationLimit = 0;
+        };
+      };
+      services.openssh = {
+        enable = mkDefault true;
+        passwordAuthentication = mkDefault false;
+      };
+      services.do-agent.enable = mkDefault true;
+      networking = {
+        hostName = mkDefault ""; # use Digital Ocean metadata server
+      };
+
+      /* Check for and wait for the metadata server to become reachable.
+       * This serves as a dependency for all the other metadata services. */
+      systemd.services.digitalocean-metadata = {
+        path = [ pkgs.curl ];
+        description = "Get host metadata provided by Digitalocean";
+        script = ''
+          set -eu
+          DO_DELAY_ATTEMPTS=0
+          while ! curl -fsSL -o $RUNTIME_DIRECTORY/v1.json http://169.254.169.254/metadata/v1.json; do
+            DO_DELAY_ATTEMPTS=$((DO_DELAY_ATTEMPTS + 1))
+            if (( $DO_DELAY_ATTEMPTS >= $DO_DELAY_ATTEMPTS_MAX )); then
+              echo "giving up"
+              exit 1
+            fi
+
+            echo "metadata unavailable, trying again in 1s..."
+            sleep 1
+          done
+          chmod 600 $RUNTIME_DIRECTORY/v1.json
+          '';
+        environment = {
+          DO_DELAY_ATTEMPTS_MAX = "10";
+        };
+        serviceConfig = {
+          Type = "oneshot";
+          RemainAfterExit = true;
+          RuntimeDirectory = "do-metadata";
+          RuntimeDirectoryPreserve = "yes";
+        };
+        unitConfig = {
+          ConditionPathExists = "!${doMetadataFile}";
+          After = [ "network-pre.target" ] ++
+            optional config.networking.dhcpcd.enable "dhcpcd.service" ++
+            optional config.systemd.network.enable "systemd-networkd.service";
+        };
+      };
+
+      /* Fetch the root password from the digital ocean metadata.
+       * There is no specific route for this, so we use jq to get
+       * it from the One Big JSON metadata blob */
+      systemd.services.digitalocean-set-root-password = mkIf cfg.setRootPassword {
+        path = [ pkgs.shadow pkgs.jq ];
+        description = "Set root password provided by Digitalocean";
+        wantedBy = [ "multi-user.target" ];
+        script = ''
+          set -eo pipefail
+          ROOT_PASSWORD=$(jq -er '.auth_key' ${doMetadataFile})
+          echo "root:$ROOT_PASSWORD" | chpasswd
+          mkdir -p /etc/do-metadata/set-root-password
+          '';
+        unitConfig = {
+          ConditionPathExists = "!/etc/do-metadata/set-root-password";
+          Before = optional config.services.openssh.enable "sshd.service";
+          After = [ "digitalocean-metadata.service" ];
+          Requires = [ "digitalocean-metadata.service" ];
+        };
+        serviceConfig = {
+          Type = "oneshot";
+        };
+      };
+
+      /* Set the hostname from Digital Ocean, unless the user configured it in
+       * the NixOS configuration. The cached metadata file isn't used here
+       * because the hostname is a mutable part of the droplet. */
+      systemd.services.digitalocean-set-hostname = mkIf (hostName == "") {
+        path = [ pkgs.curl pkgs.nettools ];
+        description = "Set hostname provided by Digitalocean";
+        wantedBy = [ "network.target" ];
+        script = ''
+          set -e
+          DIGITALOCEAN_HOSTNAME=$(curl -fsSL http://169.254.169.254/metadata/v1/hostname)
+          hostname "$DIGITALOCEAN_HOSTNAME"
+          if [[ ! -e /etc/hostname || -w /etc/hostname ]]; then
+            printf "%s\n" "$DIGITALOCEAN_HOSTNAME" > /etc/hostname
+          fi
+        '';
+        unitConfig = {
+          Before = [ "network.target" ];
+          After = [ "digitalocean-metadata.service" ];
+          Wants = [ "digitalocean-metadata.service" ];
+        };
+        serviceConfig = {
+          Type = "oneshot";
+        };
+      };
+
+      /* Fetch the ssh keys for root from Digital Ocean */
+      systemd.services.digitalocean-ssh-keys = mkIf cfg.setSshKeys {
+        description = "Set root ssh keys provided by Digital Ocean";
+        wantedBy = [ "multi-user.target" ];
+        path = [ pkgs.jq ];
+        script = ''
+          set -e
+          mkdir -m 0700 -p /root/.ssh
+          jq -er '.public_keys[]' ${doMetadataFile} > /root/.ssh/authorized_keys
+          chmod 600 /root/.ssh/authorized_keys
+        '';
+        serviceConfig = {
+          Type = "oneshot";
+          RemainAfterExit = true;
+        };
+        unitConfig = {
+          ConditionPathExists = "!/root/.ssh/authorized_keys";
+          Before = optional config.services.openssh.enable "sshd.service";
+          After = [ "digitalocean-metadata.service" ];
+          Requires = [ "digitalocean-metadata.service" ];
+        };
+      };
+
+      /* Initialize the RNG by running the entropy-seed script from the
+       * Digital Ocean metadata
+       */
+      systemd.services.digitalocean-entropy-seed = mkIf cfg.seedEntropy {
+        description = "Run the kernel RNG entropy seeding script from the Digital Ocean vendor data";
+        wantedBy = [ "network.target" ];
+        path = [ pkgs.jq pkgs.mpack ];
+        script = ''
+          set -eo pipefail
+          TEMPDIR=$(mktemp -d)
+          jq -er '.vendor_data' ${doMetadataFile} | munpack -tC $TEMPDIR
+          ENTROPY_SEED=$(grep -rl "DigitalOcean Entropy Seed script" $TEMPDIR)
+          ${pkgs.runtimeShell} $ENTROPY_SEED
+          rm -rf $TEMPDIR
+          '';
+        unitConfig = {
+          Before = [ "network.target" ];
+          After = [ "digitalocean-metadata.service" ];
+          Requires = [ "digitalocean-metadata.service" ];
+        };
+        serviceConfig = {
+          Type = "oneshot";
+        };
+      };
+
+    }
+  ];
+  meta.maintainers = with maintainers; [ arianvp eamsden ];
+}
+