at 25.11-pre 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 ++ optional config.networking.dhcpcd.enable "dhcpcd.service" 106 ++ optional config.systemd.network.enable "systemd-networkd.service"; 107 }; 108 }; 109 110 /* 111 Fetch the root password from the digital ocean metadata. 112 There is no specific route for this, so we use jq to get 113 it from the One Big JSON metadata blob 114 */ 115 systemd.services.digitalocean-set-root-password = mkIf cfg.setRootPassword { 116 path = [ 117 pkgs.shadow 118 pkgs.jq 119 ]; 120 description = "Set root password provided by Digitalocean"; 121 wantedBy = [ "multi-user.target" ]; 122 script = '' 123 set -eo pipefail 124 ROOT_PASSWORD=$(jq -er '.auth_key' ${doMetadataFile}) 125 echo "root:$ROOT_PASSWORD" | chpasswd 126 mkdir -p /etc/do-metadata/set-root-password 127 ''; 128 unitConfig = { 129 ConditionPathExists = "!/etc/do-metadata/set-root-password"; 130 Before = optional config.services.openssh.enable "sshd.service"; 131 After = [ "digitalocean-metadata.service" ]; 132 Requires = [ "digitalocean-metadata.service" ]; 133 }; 134 serviceConfig = { 135 Type = "oneshot"; 136 }; 137 }; 138 139 /* 140 Set the hostname from Digital Ocean, unless the user configured it in 141 the NixOS configuration. The cached metadata file isn't used here 142 because the hostname is a mutable part of the droplet. 143 */ 144 systemd.services.digitalocean-set-hostname = mkIf (hostName == "") { 145 path = [ 146 pkgs.curl 147 pkgs.nettools 148 ]; 149 description = "Set hostname provided by Digitalocean"; 150 wantedBy = [ "network.target" ]; 151 script = '' 152 set -e 153 DIGITALOCEAN_HOSTNAME=$(curl -fsSL http://169.254.169.254/metadata/v1/hostname) 154 hostname "$DIGITALOCEAN_HOSTNAME" 155 if [[ ! -e /etc/hostname || -w /etc/hostname ]]; then 156 printf "%s\n" "$DIGITALOCEAN_HOSTNAME" > /etc/hostname 157 fi 158 ''; 159 unitConfig = { 160 Before = [ "network.target" ]; 161 After = [ "digitalocean-metadata.service" ]; 162 Wants = [ "digitalocean-metadata.service" ]; 163 }; 164 serviceConfig = { 165 Type = "oneshot"; 166 }; 167 }; 168 169 # Fetch the ssh keys for root from Digital Ocean 170 systemd.services.digitalocean-ssh-keys = mkIf cfg.setSshKeys { 171 description = "Set root ssh keys provided by Digital Ocean"; 172 wantedBy = [ "multi-user.target" ]; 173 path = [ pkgs.jq ]; 174 script = '' 175 set -e 176 mkdir -m 0700 -p /root/.ssh 177 jq -er '.public_keys[]' ${doMetadataFile} > /root/.ssh/authorized_keys 178 chmod 600 /root/.ssh/authorized_keys 179 ''; 180 serviceConfig = { 181 Type = "oneshot"; 182 RemainAfterExit = true; 183 }; 184 unitConfig = { 185 ConditionPathExists = "!/root/.ssh/authorized_keys"; 186 Before = optional config.services.openssh.enable "sshd.service"; 187 After = [ "digitalocean-metadata.service" ]; 188 Requires = [ "digitalocean-metadata.service" ]; 189 }; 190 }; 191 192 /* 193 Initialize the RNG by running the entropy-seed script from the 194 Digital Ocean metadata 195 */ 196 systemd.services.digitalocean-entropy-seed = mkIf cfg.seedEntropy { 197 description = "Run the kernel RNG entropy seeding script from the Digital Ocean vendor data"; 198 wantedBy = [ "network.target" ]; 199 path = [ 200 pkgs.jq 201 pkgs.mpack 202 ]; 203 script = '' 204 set -eo pipefail 205 TEMPDIR=$(mktemp -d) 206 jq -er '.vendor_data' ${doMetadataFile} | munpack -tC $TEMPDIR 207 ENTROPY_SEED=$(grep -rl "DigitalOcean Entropy Seed script" $TEMPDIR) 208 ${pkgs.runtimeShell} $ENTROPY_SEED 209 rm -rf $TEMPDIR 210 ''; 211 unitConfig = { 212 Before = [ "network.target" ]; 213 After = [ "digitalocean-metadata.service" ]; 214 Requires = [ "digitalocean-metadata.service" ]; 215 }; 216 serviceConfig = { 217 Type = "oneshot"; 218 }; 219 }; 220 221 } 222 ]; 223 meta.maintainers = with maintainers; [ 224 arianvp 225 eamsden 226 ]; 227}