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