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 ];
 | |
| }
 | |
| 
 | 
