Skip to content
Snippets Groups Projects
deployment.nix 4.80 KiB
# A NixOS module which enables remotely-triggered deployment updates.
{ config, lib, pkgs, ... }:
let
  # A handy alias for our part of the configuration.
  cfg = config.services.private-storage.deployment;

  # Compute an authorized_keys line that allows the holder of a certain key to
  # execute a certain command *only*.
  restrictedKey =
    { authorizedKey, command, gridName }:
    # `restrict` means "disable all the things" then `command` means "but
    # enable running this one command" (the client does not have to supply the
    # command; if they authenticate, this is the command that will run).
    # environment lets us pass an environment variable into the process
    # started by the given command.  It only works because we configured our
    # sshd to allow this particular variable through.  By passing this value,
    # we can pin nixpkgs in the executed command to the same version
    # configured for use here.  It might be better if we just had a channel
    # the system could be configured with ... but we don't at the moment.
    "restrict,environment=\"NIXPKGS_FOR_MORPH=${pkgs.path}\",command=\"${command} ${gridName}\" ${authorizedKey}";
in {
  options = {
    services.private-storage.deployment.authorizedKey = lib.mkOption {
      type = lib.types.str;
      example = lib.literalExample ''
        ssh-ed25519 AAAAC3N...
      '';
      description = ''
        The SSH public key to authorize to trigger a deployment update.
      '';
    };
    services.private-storage.deployment.gridName = lib.mkOption {
      type = lib.types.str;
      example = lib.literalExample "staging";
      description = ''
        The name of the grid configuration to use to update this deployment.
      '';
    };
  };

  config = {
    # Configure the system to use our binary cache so that deployment updates
    # only require downloading pre-built software, not building it ourselves.
    nix = {
      binaryCachePublicKeys = [
        "saxtons.private.storage:MplOcEH8G/6mRlhlKkbA8GdeFR3dhCFsSszrspE/ZwY="
      ];
      binaryCaches = [
        "http://saxtons.private.storage"
      ];
    };

    services.openssh.extraConfig = ''
      PermitUserEnvironment=NIXPKGS_FOR_MORPH
    '';

    # Create a one-time service that will set up an ssh key that allows the
    # deployment user to authorize as root to perform the system update with
    # `morph deploy`.
    systemd.services.authorize-morph-as-root = {
      enable = true;
      serviceConfig = {
        # Tell systemd that the service is a process that runs and then exits.
        # By being "oneshot" instead of "simple" any dependencies are not
        # started until after the process exits.  We have no dependencies yet
        # but if we did it would be more correct for them to wait until we are
        # done.
        #
        # It is not clear that "oneshot" means "run once" though (maybe it
        # does, I can't tell) so the script is robust in the face of repeated