at 24.11-pre 7.0 kB view raw
1{ config, pkgs, lib, modulesPath, ... }: 2with lib; 3{ 4 imports = [ 5 (modulesPath + "/profiles/qemu-guest.nix") 6 (modulesPath + "/virtualisation/digital-ocean-init.nix") 7 ]; 8 options.virtualisation.digitalOcean = with types; { 9 setRootPassword = mkOption { 10 type = bool; 11 default = false; 12 example = true; 13 description = "Whether to set the root password from the Digital Ocean metadata"; 14 }; 15 setSshKeys = mkOption { 16 type = bool; 17 default = true; 18 example = true; 19 description = "Whether to fetch ssh keys from Digital Ocean"; 20 }; 21 seedEntropy = mkOption { 22 type = bool; 23 default = true; 24 example = true; 25 description = "Whether to run the kernel RNG entropy seeding script from the Digital Ocean vendor data"; 26 }; 27 }; 28 config = 29 let 30 cfg = config.virtualisation.digitalOcean; 31 hostName = config.networking.hostName; 32 doMetadataFile = "/run/do-metadata/v1.json"; 33 in mkMerge [{ 34 fileSystems."/" = lib.mkDefault { 35 device = "/dev/disk/by-label/nixos"; 36 autoResize = true; 37 fsType = "ext4"; 38 }; 39 boot = { 40 growPartition = true; 41 kernelParams = [ "console=ttyS0" "panic=1" "boot.panic_on_fail" ]; 42 initrd.kernelModules = [ "virtio_scsi" ]; 43 kernelModules = [ "virtio_pci" "virtio_net" ]; 44 loader.grub.devices = ["/dev/vda"]; 45 }; 46 services.openssh = { 47 enable = mkDefault true; 48 settings.PasswordAuthentication = mkDefault false; 49 }; 50 services.do-agent.enable = mkDefault true; 51 networking = { 52 hostName = mkDefault ""; # use Digital Ocean metadata server 53 }; 54 55 /* Check for and wait for the metadata server to become reachable. 56 * This serves as a dependency for all the other metadata services. */ 57 systemd.services.digitalocean-metadata = { 58 path = [ pkgs.curl ]; 59 description = "Get host metadata provided by Digitalocean"; 60 script = '' 61 set -eu 62 DO_DELAY_ATTEMPTS=0 63 while ! curl -fsSL -o $RUNTIME_DIRECTORY/v1.json http://169.254.169.254/metadata/v1.json; do 64 DO_DELAY_ATTEMPTS=$((DO_DELAY_ATTEMPTS + 1)) 65 if (( $DO_DELAY_ATTEMPTS >= $DO_DELAY_ATTEMPTS_MAX )); then 66 echo "giving up" 67 exit 1 68 fi 69 70 echo "metadata unavailable, trying again in 1s..." 71 sleep 1 72 done 73 chmod 600 $RUNTIME_DIRECTORY/v1.json 74 ''; 75 environment = { 76 DO_DELAY_ATTEMPTS_MAX = "10"; 77 }; 78 serviceConfig = { 79 Type = "oneshot"; 80 RemainAfterExit = true; 81 RuntimeDirectory = "do-metadata"; 82 RuntimeDirectoryPreserve = "yes"; 83 }; 84 unitConfig = { 85 ConditionPathExists = "!${doMetadataFile}"; 86 After = [ "network-pre.target" ] ++ 87 optional config.networking.dhcpcd.enable "dhcpcd.service" ++ 88 optional config.systemd.network.enable "systemd-networkd.service"; 89 }; 90 }; 91 92 /* Fetch the root password from the digital ocean metadata. 93 * There is no specific route for this, so we use jq to get 94 * it from the One Big JSON metadata blob */ 95 systemd.services.digitalocean-set-root-password = mkIf cfg.setRootPassword { 96 path = [ pkgs.shadow pkgs.jq ]; 97 description = "Set root password provided by Digitalocean"; 98 wantedBy = [ "multi-user.target" ]; 99 script = '' 100 set -eo pipefail 101 ROOT_PASSWORD=$(jq -er '.auth_key' ${doMetadataFile}) 102 echo "root:$ROOT_PASSWORD" | chpasswd 103 mkdir -p /etc/do-metadata/set-root-password 104 ''; 105 unitConfig = { 106 ConditionPathExists = "!/etc/do-metadata/set-root-password"; 107 Before = optional config.services.openssh.enable "sshd.service"; 108 After = [ "digitalocean-metadata.service" ]; 109 Requires = [ "digitalocean-metadata.service" ]; 110 }; 111 serviceConfig = { 112 Type = "oneshot"; 113 }; 114 }; 115 116 /* Set the hostname from Digital Ocean, unless the user configured it in 117 * the NixOS configuration. The cached metadata file isn't used here 118 * because the hostname is a mutable part of the droplet. */ 119 systemd.services.digitalocean-set-hostname = mkIf (hostName == "") { 120 path = [ pkgs.curl pkgs.nettools ]; 121 description = "Set hostname provided by Digitalocean"; 122 wantedBy = [ "network.target" ]; 123 script = '' 124 set -e 125 DIGITALOCEAN_HOSTNAME=$(curl -fsSL http://169.254.169.254/metadata/v1/hostname) 126 hostname "$DIGITALOCEAN_HOSTNAME" 127 if [[ ! -e /etc/hostname || -w /etc/hostname ]]; then 128 printf "%s\n" "$DIGITALOCEAN_HOSTNAME" > /etc/hostname 129 fi 130 ''; 131 unitConfig = { 132 Before = [ "network.target" ]; 133 After = [ "digitalocean-metadata.service" ]; 134 Wants = [ "digitalocean-metadata.service" ]; 135 }; 136 serviceConfig = { 137 Type = "oneshot"; 138 }; 139 }; 140 141 /* Fetch the ssh keys for root from Digital Ocean */ 142 systemd.services.digitalocean-ssh-keys = mkIf cfg.setSshKeys { 143 description = "Set root ssh keys provided by Digital Ocean"; 144 wantedBy = [ "multi-user.target" ]; 145 path = [ pkgs.jq ]; 146 script = '' 147 set -e 148 mkdir -m 0700 -p /root/.ssh 149 jq -er '.public_keys[]' ${doMetadataFile} > /root/.ssh/authorized_keys 150 chmod 600 /root/.ssh/authorized_keys 151 ''; 152 serviceConfig = { 153 Type = "oneshot"; 154 RemainAfterExit = true; 155 }; 156 unitConfig = { 157 ConditionPathExists = "!/root/.ssh/authorized_keys"; 158 Before = optional config.services.openssh.enable "sshd.service"; 159 After = [ "digitalocean-metadata.service" ]; 160 Requires = [ "digitalocean-metadata.service" ]; 161 }; 162 }; 163 164 /* Initialize the RNG by running the entropy-seed script from the 165 * Digital Ocean metadata 166 */ 167 systemd.services.digitalocean-entropy-seed = mkIf cfg.seedEntropy { 168 description = "Run the kernel RNG entropy seeding script from the Digital Ocean vendor data"; 169 wantedBy = [ "network.target" ]; 170 path = [ pkgs.jq pkgs.mpack ]; 171 script = '' 172 set -eo pipefail 173 TEMPDIR=$(mktemp -d) 174 jq -er '.vendor_data' ${doMetadataFile} | munpack -tC $TEMPDIR 175 ENTROPY_SEED=$(grep -rl "DigitalOcean Entropy Seed script" $TEMPDIR) 176 ${pkgs.runtimeShell} $ENTROPY_SEED 177 rm -rf $TEMPDIR 178 ''; 179 unitConfig = { 180 Before = [ "network.target" ]; 181 After = [ "digitalocean-metadata.service" ]; 182 Requires = [ "digitalocean-metadata.service" ]; 183 }; 184 serviceConfig = { 185 Type = "oneshot"; 186 }; 187 }; 188 189 } 190 ]; 191 meta.maintainers = with maintainers; [ arianvp eamsden ]; 192} 193