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