Skip to content

Instantly share code, notes, and snippets.

@sini
Last active August 28, 2026 19:28
Show Gist options
  • Select an option

  • Save sini/bc39b531823c2094014bb9b0f0d06b74 to your computer and use it in GitHub Desktop.

Select an option

Save sini/bc39b531823c2094014bb9b0f0d06b74 to your computer and use it in GitHub Desktop.
Using a Third-Party NixOS-Style Builder (finix) with Den

Using a Third-Party NixOS-Style Builder (finix) with Den

Integrating third-party NixOS-style system builders—such as Finix—into a Den flake workspace requires understanding how Den manages module collection, builder invocation, and output attribute paths.

This tutorial provides a complete architectural guide and three practical integration patterns for wiring Finix hosts into a Den-managed fleet.

Revision note. The first version of this guide was not runnable end to end. Its finixSystem calls omitted the required lib argument, it assumed Finix declares the nixpkgs.* options Den emits, and it activated host aspects through a key Den ignores. Every code block below has now been evaluated against den c7ef3f1 and finix 78fd549; §6 builds a real finix-system.drv. The three corrections are called out inline as Correction 1/2/3.


1. Overview & Finix Project Context

Finix is a minimal Linux distribution derived from NixOS base modules, designed for low-resource footprint or container environments. Key differences from standard NixOS include:

  • Init System: Uses finit (Fast Init) instead of systemd.
  • Device & Seat Management: Ships mdevd in place of eudev and seatd in place of elogind (compatibility services/udev and services/elogind modules still exist in the tree).
  • Flake Entrypoint: Uses inputs.finix.lib.finixSystem { … } to evaluate configurations instead of nixpkgs.lib.nixosSystem.

The finixSystem signature — read this first

# finix/flake.nix
lib.finixSystem =
  { lib ? null, specialArgs ? { }, modules ? [ ], ... }:
  let
    config = lib.evalModules {
      class = "nixos";
      specialArgs = lib.recursiveUpdate { modules = self.nixosModules; } specialArgs;
      modules = [ self.nixosModules.default ] ++ modules;
    };
  in
  config // { inherit (config._module.args) pkgs; inherit lib; };

Three consequences drive everything below:

  1. lib defaults to null and is dereferenced immediately. Omitting it fails with error: expected a set but found null: null, pointing at finix/flake.nix — not at your configuration. Always pass lib = inputs.nixpkgs.lib;.
  2. Every other argument is swallowed by .... pkgs, modulesPath, system and friends are accepted silently and ignored. finixSystem takes exactly lib, specialArgs, and modules.
  3. pkgs comes from inside the module system, via Finix's required nixpkgs.pkgs option — not from a builder argument.

In Den, system configuration evaluation and flake output generation are managed through three decoupled concepts:

  1. Classes (den.classes.<name>): Named payload buckets (e.g. nixos, darwin, homeManager, droid, k8s-manifests, finix). When an aspect defines a top-level key matching a registered class name (e.g. finix = { ... };), Den collects those modules for target entities during policy resolution.
  2. Instantiate Thunks (instantiate = { modules, ... }: ...): A function that receives all Den-collected class modules for an entity and invokes the target builder (inputs.finix.lib.finixSystem).
  3. Flake Output Binding (intoAttr = [ "nixosConfigurations" host.name ]): An attribute path specification telling Den where on the output flake attribute set to place the evaluated system object.

2. Background: How Den Builds nixosConfigurations

For each host in den.hosts, Den re-walks that host's scope subtree and collects every module emitted into the bucket named by host.class. It then calls:

host.instantiate {
  modules = <collected class modules> ++ [
    { nixpkgs.hostPlatform = lib.mkDefault host.system; }
  ];
}

The trailing nixpkgs.hostPlatform module is appended whenever the host spec has no pkgs attribute (nix/lib/aspects/fx/resolve.nix). host.mainModule is the fallback used only when the subtree walk yields no modules at all — it is not normally what gets passed.

Den then places the resulting evaluation object with lib.setAttrByPath ([ "flake" ] ++ host.intoAttr) (nix/lib/aspects/fx/edges/instantiate.nix), i.e. at flake.<seg1>.<seg2>….

Core Class Defaults Table

By default, Den ships with pre-registered defaults in den/nix/lib/entities/host.nix:

host.class Default instantiate Builder Default intoAttr Output Path
nixos inputs.nixpkgs.lib.nixosSystem [ "nixosConfigurations" host.name ]
darwin inputs.darwin.lib.darwinSystem [ "darwinConfigurations" host.name ]
systemManager inputs.system-manager.lib.makeSystemConfig [ "systemConfigs" host.name ]

(See reference/output.mdx ["Build pipeline"] and reference/schema.mdx in the Den docs).

Key Observations for Finix Integration

  1. The Builder is Just a Function: From Den's perspective, inputs.nixpkgs.lib.nixosSystem and inputs.finix.lib.finixSystem have the same functional contract: take modules (plus builder-specific args), and return an evaluated configuration attrset. Swapping builders is a plain option override — but the builders' argument sets differ, and Finix's is unusually small (see §1).

  2. Output Placement is Data: intoAttr is a simple list of path segments on the flake. Placed outputs appear under nixosConfigurations or finixConfigurations automatically.

  3. ⚠️ Edge Case 1 — Evaluation Crash on Unknown Class: Default instantiate and intoAttr option implementations perform a lookup indexed by config.class (.${config.class}). If a host sets class = "finix" without supplying explicit overrides for both, evaluation crashes with attribute 'finix' missing at nix/lib/entities/host.nix:120. This is not lazy — it fires on builtins.attrNames of the flake, because den.policies.system-to-os-outputs reads host.intoAttr for every host. Supplying explicit values via per-host options (Option A) or den.schema.host.imports (Option B) bypasses the lookup completely.

  4. ⚠️ Edge Case 2 — Class Bucket Isolation & Aspect Routing: Den collects the class bucket matching host.class. If existing aspects write module config under nixos = { ... }; and you declare class = "finix", those nixos blocks are ignored for that host by default. Remedy: register a route policy forwarding nixos class content into finix (shown in Option B).

  5. ⚠️ Edge Case 3 — Finix does not declare the nixpkgs options Den emits (Correction 2). Finix's modules/nixpkgs/default.nix declares exactly one option:

    options.nixpkgs.pkgs = lib.mkOption { type = lib.types.pkgs; };   # required, no default

    There is no nixpkgs.hostPlatform and no nixpkgs.config. Two Den behaviours collide with that:

    • Den appends { nixpkgs.hostPlatform = lib.mkDefault host.system; } (§2).
    • Den's always-on unfree-predicate and insecure-predicate default batteries import a module into ${host.class} that sets config.nixpkgs.config.*. A mkIf false definition still needs the option to exist, so this fires even when you use neither battery.

    Symptom: The option `nixpkgs.config' does not exist. … Did you mean `nixpkgs.pkgs'? — attributed to <class>@insecure-predicate/os. This affects Option A and Option B equally; the class name in the error changes, the failure does not. The fix is a three-line shim that declares both options and derives nixpkgs.pkgs from them (below).

  6. ⚠️ Edge Case 4 — Host aspects are not activated by den.hosts.….includes (Correction 3). A host's aspect tree is den.aspects.<hostName> plus den.schema.host.includes (nix/lib/resolve-entity.nix). Host submodules are freeform, so an includes key written directly on den.hosts.<system>.<name> is accepted and silently dropped. Use den.aspects.<hostName>.includes = [ … ] instead.

  7. Declaration Precedence & mkForce: Plain option declarations override mkOption defaults and mkDefault blocks (such as the channel-aware instantiate override in modules/den/schema/host.nix). instantiate is types.raw, whose merge accepts exactly one definition — lib.mkForce is required only when overriding another explicit plain assignment.


3. Integration Patterns

All three patterns need the nixpkgs shim from Edge Case 3. Define it once:

# modules/den/batteries/finix-nixpkgs-shim.nix
{ lib, inputs, ... }:
{
  # Declares the two options Den emits but Finix does not, and derives Finix's
  # required `nixpkgs.pkgs` from them. Gated on host.class so nixos/darwin hosts
  # are untouched — change the guard to match whichever class you build through
  # finixSystem (Option A uses "nixos").
  den.default.includes = [
    (
      { host, ... }:
      {
        name = "finix/nixpkgs-shim";
      }
      // lib.optionalAttrs ((host.class or null) == "finix") {
        finix =
          { config, ... }:
          {
            options.nixpkgs = {
              hostPlatform = lib.mkOption { type = lib.types.str; };
              config = lib.mkOption {
                type = lib.types.attrs;
                default = { };
              };
            };
            config.nixpkgs.pkgs = import inputs.nixpkgs {
              system = config.nixpkgs.hostPlatform;
              inherit (config.nixpkgs) config;
            };
          };
      }
    )
  ];
}

Option A — Minimal: Per-Host instantiate Override (Start Here)

Since Finix accepts standard NixOS modules, the simplest integration keeps class = "nixos" intact—allowing all existing nixos-class aspects and batteries to keep working—and swaps only the builder thunk on specific Finix hosts:

# modules/den.nix (or inside den.hosts)
{
  den.hosts.x86_64-linux.finix-box = {
    # class stays "nixos" (default), intoAttr stays [ "nixosConfigurations" "finix-box" ] (default).
    # Only the builder thunk is overridden:

    instantiate =
      { modules, ... }:
      inputs.finix.lib.finixSystem {
        inherit modules;
        # Correction 1: `lib` defaults to null in finixSystem and is used as
        # `lib.evalModules`. Omitting it fails with
        # "expected a set but found null: null".
        lib = inputs.nixpkgs.lib;
        # Expose flake inputs to finix/NixOS modules:
        specialArgs = { inherit inputs; };
      };
  };

  # Correction 3: aspect activation lives on den.aspects.<hostName>, NOT on the
  # host entity. `den.hosts.….includes` is a freeform key and is silently dropped.
  den.aspects.finix-box.includes = [ den.aspects.base ];
}

Note the two arguments that are gone versus the original guide: pkgs and modulesPath. Both were swallowed by finixSystem's ... and did nothing; inputs.finix.modulesPath does not exist as a flake output at all (Finix exports nixosModules, lib, formatter). Package set selection happens through the nixpkgs.pkgs shim instead.

Result: .#nixosConfigurations.finix-box is evaluated using inputs.finix.lib.finixSystem instead of lib.nixosSystem, while consuming the standard nixos class modules collected from the host's aspects. Retarget the shim's guard to (host.class or null) == "nixos" and its class key to nixos for this option.

Technical Details & Variants:

  • Builder Arguments: Den only supplies modules. Everything else is owned by the builder — for Finix that means lib and specialArgs, nothing more. Flake inputs are in scope wherever the module defining instantiate is evaluated.
  • hostPlatform Handling: Den appends { nixpkgs.hostPlatform = lib.mkDefault host.system; } for hosts without an explicit pkgs attribute. Finix does not declare that option — the shim above supplies it, and reuses it to instantiate the package set.
  • Fleet-Wide Variant: If every nixos-class host should evaluate through Finix, apply the override at the schema level instead of per-host:
    den.schema.host.imports = [
      (
        { config, ... }:
        lib.mkIf (config.class == "nixos") {
          instantiate =
            { modules, ... }:
            inputs.finix.lib.finixSystem {
              inherit modules;
              lib = inputs.nixpkgs.lib;
              specialArgs = { inherit inputs; };
            };
        }
      )
    ];
  • Module Import Variant: If you only need Finix's base modules but standard nixosSystem output is acceptable, import those modules directly into your aspects without swapping the builder thunk.

Option B — Fleet-Wide: Custom finix OS Class

If you operate a fleet of Finix machines and want the builder and output namespace to be a first-class property of the host (class = "finix"), register the class and provide class-scoped instantiate/intoAttr defaults via a schema import.

This pattern mirrors the structure of the Nix-on-Droid battery in nix-on-droid.nix, which defines class = "droid" with inputs.nix-on-droid.lib.nixOnDroidConfiguration and intoAttr = [ "nixOnDroidConfigurations" config.name ].

Step 1: Define the Finix Battery (modules/den/batteries/finix.nix)

# modules/den/batteries/finix.nix
{
  den,
  lib,
  inputs,
  ...
}:
{
  # 1. Register 'finix' in Den's class registry so aspects can write under finix = { ... }
  den.classes.finix.description = "finix (finix-community) system modules";

  # 2. Class-scoped instantiation schema import.
  # lib.mkIf (config.class == "finix") leaves nixos/darwin hosts untouched.
  den.schema.host.imports = [
    (
      { config, ... }:
      lib.mkIf (config.class == "finix") {
        instantiate =
          { modules, ... }:
          inputs.finix.lib.finixSystem {
            inherit modules;
            # Correction 1 — see §1.
            lib = inputs.nixpkgs.lib;
            specialArgs = { inherit inputs; };
          };

        # Direct output placement. Use "nixosConfigurations" to sit alongside
        # NixOS hosts, or a distinct namespace as here:
        intoAttr = [
          "finixConfigurations"
          config.name
        ];
      }
    )
  ];

  # 3. Forward existing 'nixos' aspect blocks into 'finix' class for finix hosts
  den.policies.nixos-to-finix =
    { host, ... }:
    lib.optional ((host.class or null) == "finix") (
      den.lib.policy.route {
        fromClass = "nixos";
        intoClass = "finix";
        path = [ ]; # top-level merge
      }
    );

  # Register policy in global default scope so it fires in all scopes where host is bound
  den.default.includes = [ den.policies.nixos-to-finix ];
}

Import that alongside the finix-nixpkgs-shim battery from the top of §3.

Step 2: Declare Hosts and Aspects (modules/den.nix)

# modules/den.nix
{
  imports = [
    ./den/batteries/finix.nix
    ./den/batteries/finix-nixpkgs-shim.nix
  ];

  den.hosts.x86_64-linux = {
    finix-1 = { class = "finix"; users.alice = { }; };
    finix-2 = { class = "finix"; };
    plain-nixos = { }; # untouched, still lib.nixosSystem -> nixosConfigurations
  };

  # Correction 3: activation goes on the aspect, not the host entity.
  den.aspects.finix-1.includes = [ den.aspects.base ];
  den.aspects.finix-2.includes = [ den.aspects.base ];

  # Aspects can now target 'finix' explicitly or rely on policy.route from 'nixos'
  den.aspects.base = {
    finix = {
      services.finit.enable = true;
    };
  };
}

Technical Notes on Class Selection & Home Manager:

  • Aspect Compatibility: Built-in batteries emitting ${host.class}.... (hostname, users, home-manager bridges) emit finix.... on Finix hosts. Since finixSystem evaluates NixOS-shaped modules, most of these evaluate natively — but only where Finix actually declares the option. Verify per battery.
  • The os class does NOT reach a custom class. Den's built-in os-to-host policy is hardcoded to nixos and darwin (modules/aspects/batteries/os-class.nix), so anything written under os = { … } silently vanishes on a finix host. Either write finix = { … } directly, or register your own route:
    den.policies.os-to-finix =
      { host, ... }:
      lib.optional ((host.class or null) == "finix") (
        den.lib.policy.route { fromClass = "os"; intoClass = "finix"; path = [ ]; }
      );
  • Inert Option Shims: The nixpkgs shim in §3 is one instance of a general pattern — when a Finix base module lacks an option a global battery emits, declare it inert. See the shim technique in nix-on-droid.nix.
  • Home Manager Bridge: If a Finix host requires Home Manager and inputs.home-manager has no finixModules attribute, use Den's den.lib.home-env.makeHomeEnv bridge (see the droidHome block in nix-on-droid.nix) to forward collected homeManager class payload.

Option C — Custom Non-OS Class Feeding Third-Party Tools

If you want aspects to declare structured content that a third-party tool assembles into something other than a host system (such as Kubernetes manifests via Nixidy, infrastructure via Terranix, or custom disk/OCI images), use den.lib.policy.instantiate with a custom class.

This pattern is documented in reference/policies.mdx (policy.instantiate spec) and demonstrated in tutorials/terranix-demo.mdx.

3-Step Walkthrough:

  1. Register a Custom Class:

    den.classes.k8s-manifests.description = "Kubernetes manifests collected for nixidy";

    (Note: For non-module structured payload such as lists of CRDs, manifests, or environment variables, register a companion quirk via den.quirks.* instead. See guides/quirks.mdx and nixidy.nix).

  2. Write Aspects Emitting Payload:

    den.aspects.monitoring = {
      k8s-manifests = { charts, ... }: {
        charts = [ { chart = charts.grafana.grafana; name = "grafana"; } ];
      };
    };
  3. Instantiate via Policy & Builder:

    den.policies.cluster-to-nixidy =
      { cluster, system, ... }:
      [
        (den.lib.policy.instantiate {
          name = cluster.name;
          class = "k8s-manifests"; # Bucket to collect
          instantiate =
            { modules, ... }:
            inputs.nixidy.lib.mkEnv {
              inherit modules; # Den-collected class modules
            };
          intoAttr = [ "nixidyEnvs" system cluster.name ]; # Flake output path
        })
      ];

    (Working reference: cluster-to-nixidy policy in clusters.nix).

    Per-Host Image Artifact Variant: To generate custom per-host Finix disk or OCI images alongside host system configs:

    den.policies.host-to-finix-image =
      { host, ... }:
      [
        (den.lib.policy.instantiate {
          name = host.name;
          class = "nixos";
          instantiate =
            { modules, ... }:
            <image-builder> { inherit modules; };
          intoAttr = [ "finixImages" host.system host.name ];
        })
      ];

    Per-entity isolation is automatic: policy.instantiate collects only from the target entity's scope subtree, guaranteeing host A's modules never leak into host B's artifact.


4. Decision Guide

Requirement / Goal Pattern Explanation
One or a few hosts build through finixSystem instead of nixosSystem Option A (Per-host override) Simplest path. Keeps class = "nixos" and intoAttr = [ "nixosConfigurations" ... ]. Still needs the nixpkgs shim.
Every nixos host should build through finixSystem fleet-wide Option A Variant (Schema-scoped override) Overrides default instantiate for config.class == "nixos" fleet-wide.
A fleet of Finix hosts where builder and namespace are host properties Option B (class = "finix" + schema import) Explicit class separation. Pairs with den.policies.nixos-to-finix to reuse nixos aspects.
Assemble Finix/NixOS aspects into non-OS artifacts (disk images, OCI images, K8s manifests) Option C (Custom den.classes.* + policy.instantiate) Collects class modules across entities and feeds third-party builder functions (e.g. Nixidy, Terranix).

Composition Note: Options A and B compose seamlessly with Option C. A host can evaluate finixSystem for its root system while using custom classes for extra build artifacts.


5. Checklist & Gotchas

  • Pass lib to finixSystem. It defaults to null. The failure surfaces as expected a set but found null: null inside finix/flake.nix, with a stack that usually points at Den's modules/outputs.nix — misleading, but the cause is always the missing lib.
  • Declare nixpkgs.hostPlatform and nixpkgs.config, and set nixpkgs.pkgs. See the §3 shim. Without it: The option 'nixpkgs.config' does not exist.
  • Activate aspects via den.aspects.<hostName>.includes. den.hosts.<system>.<name>.includes is a freeform key Den never reads — it fails silently, with no warning and no error.
  • pkgs Handling: Den's instantiate thunk convention is pkgs-less. Do not introduce a host-wide pkgs option, as it alters spec ? pkgs detection across all hosts (see NOTE on pkgs in nix-on-droid.nix). Setting it also suppresses the nixpkgs.hostPlatform module Den would otherwise append.
  • finit, Not systemd: Finix replaces systemd with finit. Aspect modules emitting systemd.services.* or systemd.tmpfiles will fail unless gated. Two distinct mechanisms, easy to confuse:
    • a route/deliver guard, which takes the target's module-system args:
      den.lib.policy.route {
        fromClass = "nixos";
        intoClass = "finix";
        guard = { options, ... }: options ? systemd;
      }
    • a conditional aspect, which uses meta.guard and receives a resolution context, not module args (see reference/aspects.mdx, "meta.guard / meta.aspects").
  • specialArgs: inputs is in scope wherever instantiate is defined. Passing specialArgs = { inherit inputs; }; is standard for forwarding flake inputs to modules. Finix merges it over { modules = self.nixosModules; }.
  • Output Naming: Den disambiguates colliding output paths only across different systems, by appending @<system> to the last segment. Two specs colliding on the same path and the same system are deduplicated to the last one with a lib.warn — so do not rely on collision handling to keep both.
  • Skipping Output Placement (intoAttr = [ ]): Setting intoAttr = [ ] suppresses output binding (useful for child configs consumed by parent builders).
  • Class vs. Quirk: Use classes when payload aspects emit NixOS/Finix modules. Use quirks (den.quirks.*) when payload aspects emit raw structured data.

6. End-to-End Minimal Example

The following is the exact configuration used to verify this guide. It evaluates to a real derivation against den c7ef3f1 and finix 78fd549.

# flake.nix — Den's templates/minimal shell plus a finix input
{
  description = "Minimal Den flake with a finix host";

  inputs = {
    nixpkgs.url = "https://channels.nixos.org/nixos-unstable/nixexprs.tar.xz";
    import-tree.url = "github:vic/import-tree";
    den.url = "github:denful/den";
    finix.url = "github:finix-community/finix";
  };

  outputs =
    inputs:
    (inputs.nixpkgs.lib.evalModules {
      modules = [ (inputs.import-tree ./modules) ];
      specialArgs = { inherit inputs; };
    }).config.flake;
}
# modules/den.nix
{
  den,
  lib,
  inputs,
  ...
}:
{
  imports = [ inputs.den.flakeModule ];

  den.classes.finix.description = "finix system configuration";

  den.schema.host.imports = [
    (
      { config, ... }:
      lib.mkIf (config.class == "finix") {
        instantiate =
          { modules, ... }:
          inputs.finix.lib.finixSystem {
            inherit modules;
            # finixSystem's `lib` argument defaults to null and is used as
            # `lib.evalModules` — omitting it fails with "expected a set but
            # found null".
            lib = inputs.nixpkgs.lib;
            specialArgs = { inherit inputs; };
          };
        intoAttr = [
          "finixConfigurations"
          config.name
        ];
      }
    )
  ];

  # finix declares only `nixpkgs.pkgs`. Den appends `nixpkgs.hostPlatform` and
  # its always-on unfree/insecure batteries emit `nixpkgs.config`; declare both
  # and derive `nixpkgs.pkgs` from them.
  den.default.includes = [
    (
      { host, ... }:
      {
        name = "finix/nixpkgs-shim";
      }
      // lib.optionalAttrs ((host.class or null) == "finix") {
        finix =
          { config, ... }:
          {
            options.nixpkgs = {
              hostPlatform = lib.mkOption { type = lib.types.str; };
              config = lib.mkOption {
                type = lib.types.attrs;
                default = { };
              };
            };
            config.nixpkgs.pkgs = import inputs.nixpkgs {
              system = config.nixpkgs.hostPlatform;
              inherit (config.nixpkgs) config;
            };
          };
      }
    )
  ];

  den.aspects.base.finix = {
    networking.hostName = "slab";
    fileSystems."/" = {
      device = "/dev/null";
      fsType = "ext4";
    };
  };

  den.aspects.slab.includes = [ den.aspects.base ];

  den.hosts.x86_64-linux.slab.class = "finix";
}

Verification Commands

# Evaluate the generated Finix configuration
$ nix eval .#finixConfigurations.slab.config.networking.hostName
"slab"

# Confirm the package set resolved through the nixpkgs shim
$ nix eval .#finixConfigurations.slab.pkgs.system
"x86_64-linux"

# Build the system toplevel derivation
$ nix eval .#finixConfigurations.slab.config.system.build.toplevel.drvPath
"/nix/store/…-finix-system.drv"

Note fsType = "ext4" rather than "auto": Finix's boot/initrd.nix maps each fileSystems.*.fsType onto a boot.initrd.supportedFilesystems.<fs> option, and there is no auto member.


7. Further Reading & References

Den Documentation Surface (in Den Repository docs/src/content/docs/)

  • reference/output.mdx — Build pipeline, custom instantiation, custom output paths, den.systems
  • reference/policies.mdxpolicy.instantiate, policy.route, policy.deliver, policy.provide specs
  • reference/aspects.mdxmeta.guard / meta.aspects conditional aspects
  • guides/custom-classes.mdx — Declaring custom classes, forwardTo/parentPath class fields, worked examples
  • guides/quirks.mdx — Non-module payload channels
  • reference/schema.mdx — Host/Home option reference (class, instantiate, intoAttr)
  • tutorials/terranix-demo.mdx — Complete third-party tool integration example (Terranix)
  • templates/minimal — Minimal flake shape to copy from

Working References (sini/nix-config at Commit 01c9391efbdc)

  1. modules/den/batteries/nix-on-droid.nix — Custom OS class (class = "droid") + custom builder + custom output + Home Manager bridge.
  2. modules/den/schema/host.nix — Fleet-level host schema with channel-aware mkDefault instantiate/home-manager.module.
  3. modules/den/batteries/nixidy.nix — Custom class + quirk registration, flake-level outputs derived from Den pipeline output.
  4. modules/den/policies/clusters.nixpolicy.instantiate with a third-party builder (mkEnv) per cluster/system.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment