Builtin Block Types

A few Block Types are packaged with std.

In practical terms, Block Types distinguish themselves through the actions they provide to a particular Cell Block.

It is entirely possible to define custom Block Types with custom Actions according to the needs of your project.

Arion

{root}:
/*
Use the arion for arionCompose Jobs - https://docs.hercules-ci.com/arion/

Available actions:
  - up
  - ps
  - stop
  - rm
  - config
  - arion
*/
let
  inherit (root) mkCommand;
in
  name: {
    inherit name;
    type = "arion";
    actions = {
      currentSystem,
      fragment,
      fragmentRelPath,
      target,
      inputs,
    }: let
      pkgs = inputs.nixpkgs.${currentSystem};
      cmd = "arion --prebuilt-file ${target.config.out.dockerComposeYaml}";
    in [
      (mkCommand currentSystem "up" "arion up" [pkgs.arion] ''${cmd} up "$@" '' {})
      (mkCommand currentSystem "ps" "exec this arion task to ps" [pkgs.arion] ''${cmd} ps "$@" '' {})
      (mkCommand currentSystem "stop" "arion stop" [pkgs.arion] ''${cmd} stop "$@" '' {})
      (mkCommand currentSystem "rm" "arion rm" [pkgs.arion] ''${cmd} rm "$@" '' {})
      (mkCommand currentSystem "config" "check the docker-compose yaml file" [pkgs.arion] ''${cmd} config "$@" '' {})
      (mkCommand currentSystem "arion" "pass any command to arion" [pkgs.arion] ''${cmd} "$@" '' {})
    ];
  }

Runnables (todo: vs installables)

{
  root,
  super,
}:
/*
Use the Runnables Blocktype for targets that you want to
make accessible with a 'run' action on the TUI.
*/
let
  inherit (root) mkCommand actions;
  inherit (super) addSelectorFunctor;
in
  name: {
    __functor = addSelectorFunctor;
    inherit name;
    type = "runnables";
    actions = {
      currentSystem,
      fragment,
      fragmentRelPath,
      target,
      inputs,
    }: [
      (actions.build currentSystem target)
      (actions.run currentSystem target)
    ];
  }

Installables (todo: vs runnables)

{
  root,
  super,
  nixpkgs,
}:
/*
Use the Installables Blocktype for targets that you want to
make availabe for installation into the user's nix profile.

Available actions:
  - install
  - upgrade
  - remove
  - build
  - bundle
  - bundleImage
  - bundleAppImage
*/
let
  inherit (root) mkCommand actions;
  inherit (super) addSelectorFunctor;
  l = nixpkgs.lib // builtins;
in
  name: {
    __functor = addSelectorFunctor;
    inherit name;
    type = "installables";
    actions = {
      currentSystem,
      fragment,
      fragmentRelPath,
      target,
      inputs,
    }: let
      escapedFragment = l.escapeShellArg fragment;
    in [
      (actions.build currentSystem target)
      # profile commands require a flake ref
      (mkCommand currentSystem "install" "install this target" [] ''
        # ${target}
        set -x
        nix profile install "$PRJ_ROOT#"${escapedFragment}
      '' {})
      (mkCommand currentSystem "upgrade" "upgrade this target" [] ''
        # ${target}
        set -x
        nix profile upgrade "$PRJ_ROOT#"${escapedFragment}
      '' {})
      (mkCommand currentSystem "remove" "remove this target" [] ''
        # ${target}
        set -x
        nix profile remove "$PRJ_ROOT#"${escapedFragment}
      '' {})
      # TODO: use target. `nix bundle` requires a flake ref, but we may be able to use nix-bundle instead as a workaround
      (mkCommand currentSystem "bundle" "bundle this target" [] ''
        # ${target}
        set -x
        nix bundle --bundler github:Ninlives/relocatable.nix --refresh "$PRJ_ROOT#"${escapedFragment}
      '' {})
      (mkCommand currentSystem "bundleImage" "bundle this target to image" [] ''
        # ${target}
        set -x
        nix bundle --bundler github:NixOS/bundlers#toDockerImage --refresh "$PRJ_ROOT#"${escapedFragment}
      '' {})
      (mkCommand currentSystem "bundleAppImage" "bundle this target to AppImage" [] ''
        # ${target}
        set -x
        nix bundle --bundler github:ralismark/nix-appimage --refresh "$PRJ_ROOT#"${escapedFragment}
      '' {})
    ];
  }

Pkgs

_:
/*
Use the Pkgs Blocktype if you need to construct your custom
variant of nixpkgs with overlays.

Targets will be excluded from the CLI / TUI  and thus not
slow them down.
*/
name: {
  inherit name;
  type = "pkgs";
  cli = false; # its special power
}

Devshells

{  
  root,
  super,
}:
/*
Use the Devshells Blocktype for devShells.

Available actions:
  - build
  - enter
*/
let
  inherit (root) mkCommand actions devshellDrv;
  inherit (super) addSelectorFunctor;
  inherit (builtins) unsafeDiscardStringContext;
in
  name: {
    __functor = addSelectorFunctor;
    inherit name;
    type = "devshells";
    actions = {
      currentSystem,
      fragment,
      fragmentRelPath,
      target,
      inputs,
    }: let
      developDrv = devshellDrv target;
    in [
      (actions.build currentSystem target)
      (mkCommand currentSystem "enter" "enter this devshell" [] ''
        profile_path="$PRJ_DATA_HOME/${fragmentRelPath}"
        mkdir -p "$profile_path"
        # ${developDrv}
        nix_args=(
          "${unsafeDiscardStringContext developDrv.drvPath}"
          "--no-update-lock-file"
          "--no-write-lock-file"
          "--no-warn-dirty"
          "--accept-flake-config"
          "--no-link"
          "--build-poll-interval" "0"
          "--builders-use-substitutes"
        )
        nix build "''${nix_args[@]}" --profile "$profile_path/shell-profile"
        _SHELL="$SHELL"
        eval "$(nix print-dev-env ${developDrv})"
        SHELL="$_SHELL"
        if ! [[ -v STD_DIRENV ]]; then
          if declare -F __devshell-motd &>/dev/null; then
            __devshell-motd
          fi
          exec $SHELL -i
        fi
      '' {})
    ];
  }

Nixago

{root}:
/*
Use the Nixago Blocktype for nixago pebbles.

Use Nixago pebbles to ensure files are present
or symlinked into your repository. You may typically
use this for repo dotfiles.

For more information, see: https://github.com/nix-community/nixago.

Available actions:
  - ensure
  - explore
*/
let
  inherit (root) mkCommand;
in
  name: {
    inherit name;
    type = "nixago";
    actions = {
      currentSystem,
      fragment,
      fragmentRelPath,
      target,
      inputs,
    }: let
      pkgs = inputs.nixpkgs.${currentSystem};
    in [
      (mkCommand currentSystem "populate" "populate this nixago file into the repo" [] ''
        ${target.install}/bin/nixago_shell_hook
      '' {})
      (mkCommand currentSystem "explore" "interactively explore the nixago file" [pkgs.bat] ''
        bat "${target.configFile}"
      '' {})
    ];
  }

Containers

{
  trivial,
  root,
  super,
}:
/*
Use the Containers Blocktype for OCI-images built with nix2container.

Available actions:
  - print-image
  - publish
  - load
*/
let
  inherit (root) mkCommand actions;
  inherit (super) addSelectorFunctor;
  inherit (builtins) readFile toFile;
in
  name: {
    __functor = addSelectorFunctor;
    inherit name;
    type = "containers";
    actions = {
      currentSystem,
      fragment,
      fragmentRelPath,
      target,
      inputs,
    }: let
      inherit (inputs.n2c.packages.${currentSystem}) skopeo-nix2container;
      triv = trivial.${currentSystem};
      proviso = ./containers-proviso.sh;

      tags' =
        builtins.toFile "${target.name}-tags.json" (builtins.concatStringsSep "\n" target.image.tags);
      copyFn = ''
        copy() {
          local uri prev_tag
          uri=$1
          shift

          for tag in $(<${tags'}); do
            if ! [[ -v prev_tag ]]; then
              skopeo --insecure-policy copy nix:${target} "$uri:$tag" "$@"
            else
              # speedup: copy from the previous tag to avoid superflous network bandwidth
              skopeo --insecure-policy copy "$uri:$prev_tag" "$uri:$tag" "$@"
            fi
            echo "Done: $uri:$tag"

            prev_tag="$tag"
          done
        }
      '';
    in [
      (actions.build currentSystem target)
      (mkCommand currentSystem "print-image" "print out the image.repo with all tags" [] ''
        echo
        for tag in $(<${tags'}); do
          echo "${target.image.repo}:$tag"
        done
      '' {})
      (mkCommand currentSystem "publish" "copy the image to its remote registry" [skopeo-nix2container] ''
          ${copyFn}
          copy docker://${target.image.repo}
        '' {
          meta.image = target.image.name;
          inherit proviso;
        })
      (mkCommand currentSystem "load" "load image to the local docker daemon" [skopeo-nix2container] ''
        ${copyFn}
        if command -v podman &> /dev/null; then
           echo "Podman detected: copy to local podman"
           copy containers-storage:${target.image.repo} "$@"
        fi
        if command -v docker &> /dev/null; then
           echo "Docker detected: copy to local docker"
           copy docker-daemon:${target.image.repo} "$@"
        fi
      '' {})
    ];
  }

Terra

Block type for managing Terranix configuration for Terraform.

{
  root,
  super,
}:
/*
Use the Terra Blocktype for terraform configurations managed by terranix.

Important! You need to specify the state repo on the blocktype, e.g.:

[
  (terra "infra" "[email protected]:myorg/myrepo.git")
]

Available actions:
  - init
  - plan
  - apply
  - state
  - refresh
  - destroy
*/
let
  inherit (root) mkCommand;
  inherit (super) addSelectorFunctor;
in
  name: repo: {
    inherit name;
    __functor = self: selectors: self // selectors;
    type = "terra";
    actions = {
      currentSystem,
      fragment,
      fragmentRelPath,
      target,
      inputs,
    }: let
      inherit (inputs) terranix;
      pkgs = inputs.nixpkgs.${currentSystem};

      repoFolder = with pkgs.lib;
        concatStringsSep "/" (["./nix"] ++ (init (splitString "/" fragmentRelPath)));

      git = {
        inherit repo;
        # repo = "[email protected]:myorg/myrepo.git";
        ref = "main";
        state = fragmentRelPath + "/state.json";
      };

      terraEval = import (terranix + /core/default.nix);
      terraformConfiguration = builtins.toFile "config.tf.json" (builtins.toJSON
        (terraEval {
          inherit pkgs; # only effectively required for `pkgs.lib`
          terranix_config = {
            _file = fragmentRelPath;
            imports = [target];
          };
          strip_nulls = true;
        })
        .config);

      setup = ''
        export TF_IN_AUTOMATION=1
        # export TF_INPUT=0
        export TF_DATA_DIR="$PRJ_DATA_HOME/${fragmentRelPath}"
        export TF_PLUGIN_CACHE_DIR="$PRJ_CACHE_HOME/tf-plugin-cache"
        mkdir -p "$TF_DATA_DIR"
        mkdir -p "$TF_PLUGIN_CACHE_DIR"
        dir="$PRJ_ROOT/${repoFolder}/.tf"
        mkdir -p "$PRJ_ROOT/${repoFolder}/.tf"
        cat << MESSAGE > "$PRJ_ROOT/${repoFolder}/.tf/readme.md"
        This is a tf staging area.
        It is motivated by the terraform CLI requiring to be executed in a staging area.
        MESSAGE

        if [[ -e "$dir/config.tf.json" ]]; then rm -f "$dir/config.tf.json"; fi
        jq '.' ${terraformConfiguration} > "$dir/config.tf.json"
      '';

      wrap = cmd: ''
        ${setup}
        terraform-backend-git git \
           --dir "$dir" \
           --repository ${git.repo} \
           --ref ${git.ref} \
           --state ${git.state} \
           terraform ${cmd} "$@";
      '';
    in [
      (mkCommand currentSystem "init" "tf init" [pkgs.jq pkgs.terraform pkgs.terraform-backend-git] (wrap "init") {})
      (mkCommand currentSystem "plan" "tf plan" [pkgs.jq pkgs.terraform pkgs.terraform-backend-git] (wrap "plan") {})
      (mkCommand currentSystem "apply" "tf apply" [pkgs.jq pkgs.terraform pkgs.terraform-backend-git] (wrap "apply") {})
      (mkCommand currentSystem "state" "tf state" [pkgs.jq pkgs.terraform pkgs.terraform-backend-git] (wrap "state") {})
      (mkCommand currentSystem "refresh" "tf refresh" [pkgs.jq pkgs.terraform pkgs.terraform-backend-git] (wrap "refresh") {})
      (mkCommand currentSystem "destroy" "tf destroy" [pkgs.jq pkgs.terraform pkgs.terraform-backend-git] (wrap "destroy") {})
      (mkCommand currentSystem "terraform" "pass any command to terraform" [pkgs.jq pkgs.terraform pkgs.terraform-backend-git] (wrap "") {})
    ];
  }

Data

{
  trivial,
  root,
}:
/*
Use the Data Blocktype for json serializable data.

Available actions:
  - write
  - explore

For all actions is true:
  Nix-proper 'stringContext'-carried dependency will be realized
  to the store, if present.
*/
let
  inherit (root) mkCommand;
  inherit (builtins) toJSON concatStringsSep;
in
  name: {
    inherit name;
    type = "data";
    actions = {
      currentSystem,
      fragment,
      fragmentRelPath,
      target,
      inputs,
    }: let
      inherit (inputs.nixpkgs.${currentSystem}) pkgs;
      triv = trivial.${currentSystem};

      # if target ? __std_data_wrapper, then we need to unpack from `.data`
      json = triv.writeTextFile {
        name = "data.json";
        text = toJSON (
          if target ? __std_data_wrapper
          then target.data
          else target
        );
      };
    in [
      (mkCommand currentSystem "write" "write to file" [] "echo ${json}" {})
      (mkCommand currentSystem "explore" "interactively explore" [pkgs.fx] (
        concatStringsSep "\t" ["fx" json]
      ) {})
    ];
  }

Functions

_:
/*
Use the Functions Blocktype for reusable nix functions that you would
call elswhere in the code.

Also use this for all types of modules and profiles, since they are
implemented as functions.

Consequently, there are no actions available for functions.
*/
name: {
  inherit name;
  type = "functions";
}

Anything

Note: while the implementation is the same as functions, the semantics are different. Implementations may diverge in the future.

_:
/*
Use the Anything Blocktype as a fallback.

It doesn't have actions.
*/
name: {
  inherit name;
  type = "anything";
}