now-runner 0.1.0

Nix-based distributed command runner
<p align="center">
    <img src="./images/logo.png" alt="now logo" /> <br>
</p>

---

# now

Nix-based distributed command runner.

## Status

Still an early work-in-progress. Expect breaking changes and broken functionality.

## Quick start

```bash
nix run github:EpicEric/now -- init
```

This creates a `now.nix` file in the current directory. To run it:

```bash
nix run github:EpicEric/now -- run now.nix
# --- or ---
nix run github:EpicEric/now -- run .
```

For options and examples:

```bash
nix run github:EpicEric/now -- --help
```

## Workflows

Here's a full example of `now`'s features:

```nix
{ runner, lib, ... }:
{
  name = "Optional name for the workflow";
  # Specify default job(s) to run (override with --job or --all-jobs)
  default = [ "job-1" ];
  jobs = {
    # Jobs are a sequence of steps run on a single machine, in parallel with other jobs
    job-1 =
      { pkgs, ... }:
      {
        name = "Optional name for the job";
        steps = [
          {
            name = "Optional name for the step";
            # Script to run in this step
            run = ''
              python3 --version > file
            '';
            # Packages included in the PATH of the script
            path = [
              pkgs.python313
            ];
          }
          (lib.mkIf (false) {
            name = "Skipped job";
          })
          {
            # You can set environment variables for this step (or for the whole job)
            env = {
              FOO = "Hello!";
              # Runtime-specified variable (interpolation allowed)
              BAR = "${runner.vars.BAR} (copy)";
              # Runtime-specified secret (interpolation not allowed)
              BAZ = runner.secrets.BAZ;
            };
            # You can also specify which shell to use
            shell = pkgs.python313;
            run = ''
              import os

              print(os.environ["FOO"])
            '';
            # Teardown always gets run even if the next steps fail
            teardown = ''
              import os

              # Any printed secrets get anonymized
              print(os.environ["BAZ"])
            '';
          }
          # Special step to build and upload the provided derivation to the Nix store of other runners
          (runner.upload "my-derivation" (
            pkgs.writeText "my-derivation.txt" pkgs.stdenv.hostPlatform.system
          ))
        ];
      };

    another-job =
      # Use runner.matrix to specify multiple remote jobs
      runner.matrix
      [
        {
          # Specify the target system(s) of the remote via pkgs
          pkgs = import <nixpkgs> { system = "aarch64-linux"; };
        }
        {
          # Specify the system features of the remote
          requiredSystemFeatures = [ "kvm" ];
        }
        {
          # Specify other optional parameters
          spam = "with eggs";
        }
      ]
      (
        { pkgs, spam ? null, ... }:
        {
          name = "Matrix job (${if spam != null then spam else pkgs.stdenv.hostPlatform.system})";
          # If fail-fast = true (default), the first failing matrix terminates the job
          strategy.fail-fast = false;
          # Establish that this job must run after another
          needs = [ "job-1" ];
          # Downloads the previous upload with the same name into the runner's Nix store
          env.DRV = runner.download "my-derivation";
          steps = [
            # Special step that simply builds the provided derivation
            (runner.build "some-name" pkgs.hello)
            {
              run = "echo $DRV";
            }
          ];
        }
      )
  };
}
```

## Tests

now is tested with itself. At the root of this repo:

```bash
nix run . -- run now.nix
```