diff --git a/morph/bootstrap-configuration.nix b/morph/bootstrap-configuration.nix
new file mode 100644
index 0000000000000000000000000000000000000000..c639fb97b1e598c0f68a8c2e7a9cd6a2d0d9e93e
--- /dev/null
+++ b/morph/bootstrap-configuration.nix
@@ -0,0 +1,143 @@
+#
+# This is a bare-bones configuration that can be edited slightly and then
+# dropped on a 100TB machine that is being crossgraded to NixOS.  It is
+# tailored to the specific hardware choices made for our machines at 100TB and
+# 100TB's network configuration.  The goal is to configure a system *enough*
+# that a better tool (eg morph) can take over.
+#
+#  1. Customize the variables below this comment.
+#
+#  2. Overwrite /etc/nixos/configuration.nix on Debian machine that has had
+#     NixOS installed on top of it.
+#
+#  3. Copy the generated /etc/nixos/hardware-configuration.nix from the Debian
+#     machine and add it to this repository.  We need it to build the system
+#     later.
+#
+#  4. Finish the NixOS install and reboot into a pristine NixOS system.
+#
+#  5. Specify the real configuration for this system and deploy it with morph.
+#
+let
+  # Make all these correct.  Some default values from a random system left in
+  # place as examples.
+
+  # You can probably find this interface using `ip addr` on the target system
+  # while it's still running Debian.  Pick the interface that has the public
+  # address assigned.
+  interface = "eno1";
+
+  # You probably just know what the public address is.  Make sure this agrees
+  # with what you see in `ip addr` though.
+  publicIPv4 = "69.36.183.24";
+
+  # You'll find this on the address in the `ip addr` output.  eg:
+  #
+  #  3: wlp4s0: ...
+  #    ...
+  #    inet 69.36.183.24/24 ...
+  #                      ^^ See?
+  #
+  prefixLength = 24;
+
+  # This is the default gateway address.  You can find it with `ip route` on
+  # the target system.
+  gateway = "69.36.183.1";
+
+  # And the gateway itself is reachable on a particular interface.  Most
+  # likely the same as the interface above but I don't know if this is
+  # guaranteed.  Look at the `ip route` output to be sure.
+  gatewayInterface = "eno1";
+
+  # The unique disk identifier where grub should be installed.  This should
+  # probably be sda.  You can find this value by looking for the
+  # wwn-... symlink to sda in /dev/disk/by-id/.  For example:
+  #
+  # $ ls -l /dev/disk/by-id/
+  # lrwxrwxrwx 1 root root  9 Aug 29 08:09 wwn-0x5002538d414bf195 -> ../../sda
+  #
+  # Be sure to pick the disk identifier and not the identifier of one of the
+  # partitions!
+  grubDeviceID = "wwn-0x5000c500936410b9";
+
+  # This is whatever ssh public key is appropriate at the time.  I'm leaving
+  # mine here for now.
+  rootPublicKey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIN4GenAY/YLGuf1WoMXyyVa3S9i4JLQ0AG+pt7nvcLlQ exarkun@baryon";
+
+  # Stop!  I hope you're done when you get here.  If you have to modify
+  # anything below this point the expression should probably be refactored and
+  # another variable added controlling whatever new thing you need to control.
+  # Open an issue: https://github.com/PrivateStorageio/PrivateStorageio/issues/new
+in
+# Define a function that ignores all its arguments.  We don't need any of them
+# for now.
+{ ... }:
+{
+  # Load the hardware configuration for this host.  This is generated by
+  # nixos-generate-config on the target host.  There is no such file checked
+  # in to the repository because it necessarily varies from host to host.  For
+  # example, it includes the disk id of the root partition.  We just rely on
+  # the tool to generate the correct configuration and then we load it from
+  # here.
+  imports =
+    [ # Include the results of the hardware scan.
+      ./hardware-configuration.nix
+    ];
+
+  # Configure the bootloader how we like.
+  boot.loader.timeout = 1;
+  boot.loader.grub.enable = true;
+  boot.loader.grub.version = 2;
+  boot.loader.grub.device = "/dev/disk/by-id/${grubDeviceID}";
+
+  # Let me in to do subsequent configuration.  This makes the machine wide
+  # open.  We might consider locking this down a bit more.  For example, we
+  # should only need SSH access for the next step.  However, there's basically
+  # nothing else on the system right now so it's not an extreme risk to just
+  # turn off the firewall.  Initially this was the approach to make sure I
+  # wouldn't get locked out of a system working perfectly well but with an
+  # overly restrictive firewall (since that case basically makes the machine a
+  # brick to me).
+  networking.firewall.enable = false;
+
+  # Also, turn on the OpenSSH server so I (morph, really) can log in and make
+  # further changes.
+  services.openssh.enable = true;
+
+  # Grant root access to the holder of the configured key.  We don't bother
+  # setting a password because keys are better.  We also don't configure any
+  # additional users because that will happen later.
+  users.users.root.openssh.authorizedKeys.keys = [
+    rootPublicKey
+  ];
+
+  # Provide the static network configuration.  100TB doesn't use DHCP so turn
+  # off our client.
+  networking.dhcpcd.enable = false;
+
+  # Put the configured address on the configured interface.
+  networking.interfaces = {
+    "${interface}".ipv4.addresses = [
+      { address = publicIPv4; inherit prefixLength; }
+    ];
+  };
+  # And set up the configured route as the default.
+  networking.defaultGateway = {
+    address = gateway;
+    interface = gatewayInterface;
+  };
+  # I don't know if 100TB provides nameservers but these are pretty safe in
+  # general.  This may not be strictly required to get the NixOS install
+  # bootable but a lot of tools have a dependency on being able to resolve
+  # names (for example, the Nix system configuration tool).
+  networking.nameservers = [
+    "4.2.2.1"
+    "8.8.8.8"
+  ];
+
+  # This value determines the NixOS release with which your system is to be
+  # compatible, in order to avoid breaking some software such as database
+  # servers. You should change this only after NixOS release notes say you
+  # should.
+  system.stateVersion = "19.03"; # Did you read the comment?
+}
diff --git a/nixos/modules/100tb.nix b/nixos/modules/100tb.nix
new file mode 100644
index 0000000000000000000000000000000000000000..243da0dd72913ed4f582b52ca5cfe0494936c744
--- /dev/null
+++ b/nixos/modules/100tb.nix
@@ -0,0 +1,131 @@
+# A NixOS module which configures a system that is hosted by 100TB.  Each of
+# our servers hosted with 100TB will probably import this module and pass it
+# the minimum system configuration to get the server to boot and accept
+# administrative ssh connections.
+#
+# A NixOS module is defined as a Nix expression language function.
+{
+  # This contains generally useful library functionality provided by nixpkgs.
+  # These are things like string manipulation and, notably for us, a library
+  # for defining options for configuring moduless.
+  lib,
+
+  # This is all of the configuration for a particular system where this module
+  # might be instantiated.  For any system where we want the 100TB module to
+  # be active, this should have the 100TB configuration details (IP, gateway,
+  # etc).
+  config,
+
+  # More parameters exist and are accepted but we don't need them so we ignore them.
+  ...
+}:
+let
+  # Pull out the configuration for this module for convenient use later.  The
+  # module name is quoted because `1` makes `100tb` look an awful lot like it
+  # should be a number.
+  cfg = config."100tb".config;
+
+  # Define the API to this module.  Everything in `options` is about
+  # specifying what kind of values we expect to be given.  This is both
+  # human-facing documentation as well as guidance to NixOS about acceptable
+  # values (mainly by type) so it can automatically reject certain bogus
+  # values.  This value is in the `let` to make the code below a little easier
+  # to read.  See below where we use it.
+  options = {
+    interface = lib.mkOption
+    { type = lib.types.str;
+      example = lib.literalExample "eno0";
+      description = "The name of the network interface on which to configure a static address.";
+
+    };
+    publicIPv4 = lib.mkOption
+    { type = lib.types.str;
+      example = lib.literalExample "192.0.2.0";
+      description = "The IPv4 address to statically assign to `interface`.";
+    };
+    prefixLength = lib.mkOption
+    { type = lib.types.int;
+      example = lib.literalExample 24;
+      description = "The statically configured network's prefix length.";
+    };
+    gateway = lib.mkOption
+    { type = lib.types.str;
+      example = lib.literalExample "192.0.2.1";
+      description = "The statically configured address of the network gateway.";
+    };
+    gatewayInterface = lib.mkOption
+    { type = lib.types.str;
+      example = lib.literalExample "eno0";
+      description = "The name of the network interface for the default route.";
+      default = cfg.interface;
+    };
+    grubDeviceID = lib.mkOption
+    { type = lib.types.str;
+      example = lib.literalExample "wwn-0x5000c500936410b9";
+      description = "The ID of the disk on which to install grub.";
+    };
+    rootPublicKey = lib.mkOption
+    { type = lib.types.str;
+      example = lib.literalExample "ssh-ed25519 AAAA... username@host";
+      description = "The public key to install for the root user.";
+    };
+  };
+in {
+  # Here we actually define the module's options.  They're what we said they
+  # were above, all bundled up into a "submodule" which is really just a set
+  # of options.
+  options =
+  { "100tb".config = lib.mkOption
+    { type = lib.types.submodule { inherit options; };
+      description = "Host-specific configuration relevant to a 100TB system.";
+    };
+  };
+
+  # Now compute the configuration that results from whatever values were
+  # supplied for our options.  A lot of this is currently very similar to
+  # what's in bootstrap-configuration.nix (which is well commented).  The
+  # similarity makes sense - both that configuration and this one need to get
+  # a 100TB machine to boot and let an admin SSH in.
+  #
+  # Values that go into `config` here are merged into values that go into
+  # `config` in any other active modules.  Basically, everything in this
+  # `config` is treated as if it were in the configuration set defined by
+  # `/etc/nixos/configuration.nix`.  The module just gives us a way to factor
+  # separate concerns separately and make reuse easier.
+  #
+  # Note that this is not where Tahoe-LAFS configuration goes.  It's just
+  # about getting base platform into good shape.
+  #
+  # Perhaps at some point this can be refactored to remove the duplication.
+  # It's slightly tricky because we don't want to introduce any external
+  # dependencies to bootstrap-configuration.nix because that would make it
+  # harder to deploy in the bootstrap environment.
+  config =
+  { boot.loader.grub.enable = true;
+    boot.loader.grub.version = 2;
+    boot.loader.grub.device = "/dev/disk/by-id/${cfg.grubDeviceID}";
+
+    boot.loader.timeout = 1;
+    networking.firewall.enable = false;
+    services.openssh.enable = true;
+
+    users.users.root.openssh.authorizedKeys.keys = [
+      cfg.rootPublicKey
+    ];
+
+    networking.dhcpcd.enable = false;
+    networking.interfaces = {
+      "${cfg.interface}".ipv4.addresses = [
+        { address = cfg.publicIPv4; inherit (cfg) prefixLength; }
+      ];
+    };
+    networking.defaultGateway = {
+      address = cfg.gateway;
+      interface = cfg.gatewayInterface;
+    };
+    networking.nameservers = [
+      "4.2.2.1"
+      "8.8.8.8"
+    ];
+  };
+}