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" + ]; + }; +}