om ci

om ci runs continuous integration (CI)-friendly builds for your project. It builds all outputs in the flake, or optionally its sub-flakes. You can run om ci locally or in an actual CI envirnoment, like GitHub Actions. Using devour-flake it will automatically build the following outputs:

TypeOutput Key
Standard flake outputspackages, apps, checks, devShells
NixOSnixosConfigurations.*
nix-darwindarwinConfigurations.*
home-managerlegacyPackages.${system}.homeConfigurations.*

A result symlink is also produced, containing a JSON of all built paths. See here.

Basic Usage

om ci run accepts any valid flake URL or a Github PR URL.

# Run CI on current directory flake
$ om ci # Or `om ci run` or `om ci run .`

# Run CI on a local flake (default is $PWD)
$ om ci run ~/code/myproject

# Pass custom arguments to `nix` after '--'
$ om ci run ~/code/myproject -- --accept-flake-config

# Run CI on a github repo
$ om ci run github:hercules-ci/hercules-ci-agent

# Run CI on a github PR
$ om ci run https://github.com/srid/emanote/pull/451

# Run CI only the selected sub-flake
$ git clone https://github.com/srid/haskell-flake && cd haskell-flake
$ om ci run .#default.dev

# Run CI remotely over SSH
$ om ci run --on ssh://myname@myserver ~/code/myproject

Just like nix build, om ci will produce a result symlink that contains a JSON of all store paths built. Use options --out-link <PATH> and --no-link to control this behaviour.

As long as this symlink exists, your built paths will survive garbage collection, because the closure of this symlink contains the entire build closure.

Note that in order to include all build dependencies, you should pass --include-all-dependencies, viz.:

om ci run --include-all-dependencies | xargs cachix push mycache

The above command will push the entire build closure (runtime and build dependencies) to the given cache.

Using in Github Actions

In addition to serving the purpose of being a “local CI”, om ci can be used in Github Actions to enable CI for your GitHub repositories.

Standard Runners

Add this to your workflow file (.github/workflows/ci.yml) to build all flake outputs using GitHub provided runners:

      - uses: actions/checkout@v4
      - uses: DeterminateSystems/nix-installer-action@main
      - name: Install omnix
        run: nix --accept-flake-config profile install "github:juspay/omnix"
      - run: om ci

Self-hosted Runners with Job Matrix

Here’s a more advanced example that configures a job matrix. This is useful when you want to run the CI on multiple systems (e.g. aarch64-linux, aarch64-darwin), each captured as a separate job by GitHub, as shown in the screenshot below. It also, incidentally, demonstrates how to use self-hosted runners.

The om ci gh-matrix command outputs the matrix JSON for creating a matrix of job variations. An example configuration, using self-hosted runners, is shown below.

note

This currently requires an explicit CI configuration in your flake, setting om.ci.default.root.dir to ..

# Run on aarch64-linux and aarch64-darwin
jobs:
  configure:
    runs-on: x86_64-linux
    outputs:
      matrix: ${{ steps.set-matrix.outputs.matrix }}
    steps:
     - uses: actions/checkout@v4
     - id: set-matrix
       run: |
         set -euxo pipefail
         MATRIX="$(om ci gh-matrix --systems=x86_64-linux,aarch64-darwin | jq -c .)"
         echo "matrix=$MATRIX" >> $GITHUB_OUTPUT
  nix:
    runs-on: ${{ matrix.system }}
    needs: configure
    strategy:
      matrix: ${{ fromJson(needs.configure.outputs.matrix) }}
      fail-fast: false
    steps:
      - uses: actions/checkout@v4
      - run: om ci run --systems "${{ matrix.system }}" ".#default.${{ matrix.subflake }}"

tip

If your builds fail due to GitHub’s rate limiting, consider passing --extra-access-tokens (see an example PR).

Configuring

By default, om ci will build the top-level flake, but you can tell it to build sub-flakes (here, ./dir1 and ./dir2) by adding the following to your Om configuration:

# myproject/flake.nix
{
  om.ci.default = {
    dir1 = {
      dir = "dir1";
    };
    dir2 = {
      dir = "dir2";
      overrideInputs.myproject = ./.;
    };
  }
}

You can have more than one CI configuration. For eg., om ci run .#foo will run the configuration from om.ci.foo flake output.

Custom CI actions

You can define custom CI actions in your flake, which will be run as part of om ci run. For example, to run tests in the nix develop shell:

{
  om.ci.default = {
    root = {
      dir = ".";
      steps = {
        # The build step is enabled by default. It builds all flake outputs.
        build.enable = true;
        # Other steps include: lockfile & flake-check

        # Users can define custom steps to run any arbitrary flake app or devShell command.
        custom = {
          # Here, we run cargo tests in the nix shell
          # This equivalent to `nix develop .#default -c cargo test`
          cargo-test = {
            type = "devshell";
            # name = "default"
            command = [ "cargo" "test" ];
          };

          # We can also flake apps
          # This is equivalent to `nix run .#check-closure-size`
          closure-size = {
            type = "app";
            name = "check-closure-size";
          };
        };
      };
    };
  };
}

For a real-world example of custom steps, checkout Omnix’s configuration.

Remote CI

Omnix can run CI over SSH.

om ci run --on ssh://myname@myserver ~/code/myproject

What this does:

  1. Copy the flake source to the remote server, and run om ci there
  2. Copy the built paths back to local store

Options

  • Pass copy-inputs=true if you wish to copy all flake inputs recursively. This is useful if you have private Git inputs. For example, om ci run --on "ssh://myname@myserver?copy-inputs=true" ~/code/myproject
  • Omnix copies the results back to local store, unless --no-link was passed.

Examples

Some real-world examples of how om ci is used with specific configurations:

What it does

  • Check that the Nix version is not tool old (using om health)
  • Determine the list of flakes in the repo to build
    • By default, this is the root flake.
    • The user can also explicitly specify multiple sub-flakes in om.ci.default output of their root flake.
  • For each (sub)flake identified, om ci run will run the following steps:
    • Check that flake.lock is up to date, if applicable.
    • Build all flake outputs, using devour-flake1
      • Then, print the built store paths to stdout
    • If the flake-check step is enabled (example), run nix flake check
    • Run user defined custom steps
1

Support for flake-schemas is planned

See also

  • github-nix-ci - A simple NixOS & nix-darwin module for self-hosting GitHub runners
  • jenkins-nix-ci - Jenkins NixOS module that supports nixci (predecessor of om ci) as a Groovy function
  • cachix-push - A flake-parts module that provides an app to enable whitelisted pushing and pinning of store paths to cachix.