Skip to content

now

now is a command runner based on Nix. It allows for distributed builds of reproducible scripts, with control over how and where they should run.

Examples

{
  default = [ "serve" ];

  jobs = {
    build = { pkgs, ... }: {
      name = "Build";
      steps = [
        {
          path = [ pkgs.zola ];
          run = "zola build";
        }
        {
          run = "echo Done!";
        }
      ];
    };

    serve = { pkgs, ... }: {
      name = "Serve";
      steps = [
        {
          path = [ pkgs.zola ];
          run = ''
            echo Press Ctrl-C to quit.
            zola serve
          '';
        }
      ];
    };
  };
}
{
  # Run all tasks with just `now run check`
  check = {
    needs = [
      "lint"
      "format"
      "build"
      "test"
    ];
  };

  lint = { pkgs, ... }: {
    steps = [
      {
        run = ''
          cargo clippy --fix --all --allow-dirty
        '';
        path = [
          pkgs.cargo
          pkgs.clippy
        ];
        sandbox = {
          enable = true;
          writableDirectory = true;
        };
      }
    ];
  }

  format = { pkgs, ... }: {
    needs = [ "lint" ];
    steps = [
      {
        run = ''
          cargo fmt --all
        '';
        path = [
          pkgs.cargo
          pkgs.rustfmt
        ];
        sandbox = {
          enable = true;
          writableDirectory = true;
        };
      }
    ];
  };

  build = { pkgs, ... }: {
    needs = [ "lint" ];
    # Create a fresh temporary directory for builds
    checkout = "clone";
    steps = [
      {
        run = ''
          cargo build
        '';
        path = [
          pkgs.cargo
          pkgs.rustc
        ];
        sandbox.enable = true;
      }
    ];
  };

  test = { pkgs, ... }: {
    needs = [ "build" ];
    steps = [
      {
        run = ''
          cargo nextest run
        '';
        path = [
          pkgs.cargo
          pkgs.cargo-nextest
          pkgs.rustc
        ];
        sandbox.enable = true;
      }
    ];
  };
}
{ runner, ... }: {
  jobs.docker =
    runner.matrix
    [
      {
        pkgs = import <nixpkgs> { system = "x86_64-linux"; };
        requiredSystemFeatures = [ "docker" ];
      }
      {
        pkgs = import <nixpkgs> { system = "aarch64-linux"; };
        requiredSystemFeatures = [ "docker" ];
      }
    ]
    (
      { pkgs, ... }:
      let
        inherit (pkgs.stdenv.hostPlatform) system;
      in
      {
        name = "Docker (${system})";
        steps = [
          (runner.steps.upload {
            name = "docker-image-${system}";
            deriv = pkgs.dockerTools.buildLayeredImage {
              name = "docker.io/${runner.var "DOCKERHUB_USERNAME"}/hello";
              tag = system;
              config.Entrypoint = [ (lib.getExe pkgs.hello) ];
            };
          })
          {
            name = "Login to DockerHub";
            env = {
              DOCKERHUB_PUSH_TOKEN = runner.secret "DOCKERHUB_PUSH_TOKEN";
              DOCKERHUB_USERNAME = runner.var "DOCKERHUB_USERNAME";
            };
            run = ''
              echo $DOCKERHUB_PUSH_TOKEN | docker login --password-stdin --username $DOCKERHUB_USERNAME docker.io
            '';
            teardown = ''
              docker logout docker.io
            '';
            path = [ pkgs.docker ];
          }
          {
            name = "Push image";
            env.DOCKER_IMAGE = runner.download "docker-image-${system}";
            run = "docker push $DOCKER_IMAGE";
            path = [ pkgs.docker ];
          }
        ];
      }
    );
  };
}

Core concepts

now is separated into three levels:

  • Workflows: The specification of now and its recipes. Inspired by GitHub Actions, with a touch of just.
  • Jobs: Each individual recipe in a workflow. These can depend on other jobs and run on multiple machines at once.
  • Steps: The individual scripts run as part of your jobs, normally written in a scripting language like bash or Python.

The now.nix file format lets you specify these using Nix.