summaryrefslogtreecommitdiffstats
path: root/nixos
diff options
context:
space:
mode:
authorEdward Amsden <edward.amsden@plowtech.net>2019-03-27 20:52:28 -0400
committerarcnmx <arcnmx@users.noreply.github.com>2019-11-24 08:11:33 -0800
commit8bba28260af21c1703cdf8aa0d19c8498a78fb81 (patch)
tree045230219a98c8a6cd0dec13913a12b42aa500bc /nixos
parent7cb0bc30e6a4709a3e3e635e4b2f3bd6d08a8865 (diff)
nixos/digital-ocean-image: init
Diffstat (limited to 'nixos')
-rw-r--r--nixos/modules/virtualisation/digital-ocean-config.nix197
-rw-r--r--nixos/modules/virtualisation/digital-ocean-image.nix69
-rw-r--r--nixos/modules/virtualisation/digital-ocean-init.nix95
3 files changed, 361 insertions, 0 deletions
diff --git a/nixos/modules/virtualisation/digital-ocean-config.nix b/nixos/modules/virtualisation/digital-ocean-config.nix
new file mode 100644
index 000000000000..88cb0cd450e8
--- /dev/null
+++ b/nixos/modules/virtualisation/digital-ocean-config.nix
@@ -0,0 +1,197 @@
+{ 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 ];
+}
+
diff --git a/nixos/modules/virtualisation/digital-ocean-image.nix b/nixos/modules/virtualisation/digital-ocean-image.nix
new file mode 100644
index 000000000000..b582e235d435
--- /dev/null
+++ b/nixos/modules/virtualisation/digital-ocean-image.nix
@@ -0,0 +1,69 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+let
+ cfg = config.virtualisation.digitalOceanImage;
+in
+{
+
+ imports = [ ./digital-ocean-config.nix ];
+
+ options = {
+ virtualisation.digitalOceanImage.diskSize = mkOption {
+ type = with types; int;
+ default = 4096;
+ description = ''
+ Size of disk image. Unit is MB.
+ '';
+ };
+
+ virtualisation.digitalOceanImage.configFile = mkOption {
+ type = with types; nullOr path;
+ default = null;
+ description = ''
+ A path to a configuration file which will be placed at
+ <literal>/etc/nixos/configuration.nix</literal> and be used when switching
+ to a new configuration. If set to <literal>null</literal>, a default
+ configuration is used that imports
+ <literal>(modulesPath + "/virtualisation/digital-ocean-config.nix")</literal>.
+ '';
+ };
+
+ virtualisation.digitalOceanImage.compressionMethod = mkOption {
+ type = types.enum [ "gzip" "bzip2" ];
+ default = "gzip";
+ example = "bzip2";
+ description = ''
+ Disk image compression method. Choose bzip2 to generate smaller images that
+ take longer to generate but will consume less metered storage space on your
+ Digital Ocean account.
+ '';
+ };
+ };
+
+ #### implementation
+ config = {
+
+ system.build.digitalOceanImage = import ../../lib/make-disk-image.nix {
+ name = "digital-ocean-image";
+ format = "qcow2";
+ postVM = let
+ compress = {
+ "gzip" = "${pkgs.gzip}/bin/gzip";
+ "bzip2" = "${pkgs.bzip2}/bin/bzip2";
+ }.${cfg.compressionMethod};
+ in ''
+ ${compress} $diskImage
+ '';
+ configFile = if cfg.configFile == null
+ then config.virtualisation.digitalOcean.defaultConfigFile
+ else cfg.configFile;
+ inherit (cfg) diskSize;
+ inherit config lib pkgs;
+ };
+
+ };
+
+ meta.maintainers = with maintainers; [ arianvp eamsden ];
+
+}
diff --git a/nixos/modules/virtualisation/digital-ocean-init.nix b/nixos/modules/virtualisation/digital-ocean-init.nix
new file mode 100644
index 000000000000..02f4de009fa8
--- /dev/null
+++ b/nixos/modules/virtualisation/digital-ocean-init.nix
@@ -0,0 +1,95 @@
+{ config, pkgs, lib, ... }:
+with lib;
+let
+ cfg = config.virtualisation.digitalOcean;
+ defaultConfigFile = pkgs.writeText "digitalocean-configuration.nix" ''
+ { modulesPath, lib, ... }:
+ {
+ imports = lib.optional (builtins.pathExists ./do-userdata.nix) ./do-userdata.nix ++ [
+ (modulesPath + "/virtualisation/digital-ocean-config.nix")
+ ];
+ }
+ '';
+in {
+ options.virtualisation.digitalOcean.rebuildFromUserData = mkOption {
+ type = types.bool;
+ default = true;
+ example = true;
+ description = "Whether to reconfigure the system from Digital Ocean user data";
+ };
+ options.virtualisation.digitalOcean.defaultConfigFile = mkOption {
+ type = types.path;
+ default = defaultConfigFile;
+ defaultText = ''
+ The default configuration imports user-data if applicable and
+ <literal>(modulesPath + "/virtualisation/digital-ocean-config.nix")</literal>.
+ '';
+ description = ''
+ A path to a configuration file which will be placed at
+ <literal>/etc/nixos/configuration.nix</literal> and be used when switching to
+ a new configuration.
+ '';
+ };
+
+ config = {
+ systemd.services.digitalocean-init = mkIf cfg.rebuildFromUserData {
+ description = "Reconfigure the system from Digital Ocean userdata on startup";
+ wantedBy = [ "network-online.target" ];
+ unitConfig = {
+ ConditionPathExists = "!/etc/nixos/do-userdata.nix";
+ After = [ "digitalocean-metadata.service" "network-online.target" ];
+ Requires = [ "digitalocean-metadata.service" ];
+ X-StopOnRemoval = false;
+ };
+ serviceConfig = {
+ Type = "oneshot";
+ RemainAfterExit = true;
+ };
+ restartIfChanged = false;
+ path = [ pkgs.jq pkgs.gnused pkgs.gnugrep pkgs.systemd config.nix.package config.system.build.nixos-rebuild ];
+ environment = {
+ HOME = "/root";
+ NIX_PATH = concatStringsSep ":" [
+ "/nix/var/nix/profiles/per-user/root/channels/nixos"
+ "nixos-config=/etc/nixos/configuration.nix"
+ "/nix/var/nix/profiles/per-user/root/channels"
+ ];
+ };
+ script = ''
+ set -e
+ echo "attempting to fetch configuration from Digital Ocean user data..."
+ userData=$(mktemp)
+ if jq -er '.user_data' /run/do-metadata/v1.json > $userData; then
+ # If the user-data looks like it could be a nix expression,
+ # copy it over. Also, look for a magic three-hash comment and set
+ # that as the channel.
+ if nix-instantiate --parse $userData > /dev/null; then
+ channels="$(grep '^###' "$userData" | sed 's|###\s*||')"
+ printf "%s" "$channels" | while read channel; do
+ echo "writing channel: $channel"
+ done
+
+ if [[ -n "$channels" ]]; then
+ printf "%s" "$channels" > /root/.nix-channels
+ nix-channel --update
+ fi
+
+ echo "setting configuration from Digital Ocean user data"
+ cp "$userData" /etc/nixos/do-userdata.nix
+ if [[ ! -e /etc/nixos/configuration.nix ]]; then
+ install -m0644 ${cfg.defaultConfigFile} /etc/nixos/configuration.nix
+ fi
+ else
+ echo "user data does not appear to be a Nix expression; ignoring"
+ exit
+ fi
+
+ nixos-rebuild switch
+ else
+ echo "no user data is available"
+ fi
+ '';
+ };
+ };
+ meta.maintainers = with maintainers; [ arianvp eamsden ];
+}