198 lines
		
	
	
		
			6.9 KiB
		
	
	
	
		
			Nix
		
	
	
	
	
	
			
		
		
	
	
			198 lines
		
	
	
		
			6.9 KiB
		
	
	
	
		
			Nix
		
	
	
	
	
	
{ 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 ];
 | 
						|
}
 | 
						|
 |