about summary refs log tree commit diff
path: root/ops/modules/auto-deploy.nix
# Defines a service for automatically and periodically calling depot's
# rebuild-system on a NixOS machine.
#
# Deploys can be stopped in emergency situations by creating an empty
# file called `stop` in the state directory of the auto-deploy service
# (typically /var/lib/auto-deploy).
{ depot, config, lib, pkgs, ... }:

let
  cfg = config.services.depot.auto-deploy;
  description = "to automatically rebuild the current system's NixOS config from the latest checkout of depot";

  rebuild-system = depot.ops.nixos.rebuildSystemWith "$STATE_DIRECTORY/deploy";
  deployScript = pkgs.writeShellScript "auto-deploy" ''
    set -ueo pipefail

    if [[ $EUID -ne 0 ]]; then
      echo "Oh no! Only root is allowed to run auto-deploy!" >&2
      exit 1
    fi

    if [[ -f $STATE_DIRECTORY/stop ]]; then
      echo "stop file exists in $STATE_DIRECTORY, not deploying!" >&2
      exit 1
    fi

    readonly depot=$STATE_DIRECTORY/depot.git
    readonly deploy=$STATE_DIRECTORY/deploy
    readonly git="git -C $depot"

    # find-or-create depot
    if [ ! -d $depot ]; then
      # cannot use $git here because $depot doesn't exist
      git clone --bare ${cfg.git-remote} $depot
    fi

    function cleanup() {
      $git worktree remove $deploy
    }
    trap cleanup EXIT

    $git fetch origin
    $git worktree add --force $deploy FETCH_HEAD
    # unsure why, but without this switch-to-configuration attempts to install
    # NixOS in $STATE_DIRECTORY
    (cd / && ${rebuild-system}/bin/rebuild-system)
  '';
in {
  options.services.depot.auto-deploy = {
    enable = lib.mkEnableOption description;

    git-remote = lib.mkOption {
      type = lib.types.str;
      default = "https://cl.tvl.fyi/depot.git";
      description = ''
        The (possibly remote) repository from which to clone as specified by the
        GIT URLS section of `man git-clone`.
      '';
    };

    interval = lib.mkOption {
      type = lib.types.str;
      example = "1h";
      description = ''
        Interval between Nix builds, specified in systemd.time(7) format.
      '';
    };
  };

  config = lib.mkIf cfg.enable {
    systemd.services.auto-deploy = {
      inherit description;
      script = "${deployScript}";
      path = with pkgs; [
        bash
        git
        gnutar
        gzip
      ];
      after = [ "network-online.target" ];
      wants = [ "network-online.target" ];

      # We need to prevent NixOS from interrupting us while it attempts to
      # restart systemd units.
      restartIfChanged = false;

      serviceConfig = {
        Type = "oneshot";
        StateDirectory = "auto-deploy";
      };
    };

    systemd.timers.auto-deploy = {
      inherit description;
      wantedBy = [ "multi-user.target" ];

      timerConfig = {
        OnActiveSec = "1";
        OnUnitActiveSec = cfg.interval;
      };
    };
  };
}