This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Flake FHS (Flake Flake Hierarchy Standard) is a framework for Nix flakes that automatically generates flake outputs from a standardized directory structure, eliminating the need to write repetitive flake.nix boilerplate code.
The framework implements an automatic mapping from directory structure to flake outputs:
| Subdirectories (Aliases) | File Pattern | Special Files | Recursive | Generated Output | Nix Command |
|---|---|---|---|---|---|
packages (pkgs) |
<name>.nix or <name>/package.nix |
scope.nix |
✅ | packages.<system>.<name> |
nix build .#<name> |
nixosModules (modules) |
<name>/... |
options.nix, default.nix |
✅ | nixosModules.<name> |
- |
nixosConfigurations (hosts, profiles) |
<name>/configuration.nix |
default.nix |
✅ | nixosConfigurations.<name> |
nixos-rebuild --flake .#<name> |
apps |
<name>.nix or <name>/package.nix |
scope.nix |
✅ | apps.<system>.<name> |
nix run .#<name> |
devShells (shells) |
<name>.nix |
default.nix |
✅ | devShells.<system>.<name> |
nix develop .#<name> |
templates |
<name>/ |
flake.nix |
❌ | templates.<name> |
nix flake init --template <url>#<name> |
lib (utils, tools) |
<name>.nix |
- | ✅ | lib.<name> |
nix eval .#lib.<name> |
checks |
<name>.nix or <name>/package.nix |
scope.nix |
✅ | checks.<system>.<name> |
nix flake check .#<name> |
Host directories can contain a default.nix file that exports host-specific metadata. This is evaluated before NixOS modules to configure the globally shared pkgs instance accurately for that host.
# hosts/my-host/default.nix
{
system = "x86_64-linux";
nixpkgs = {
config = { allowUnfree = true; cudaSupport = true; };
overlays = [ /* overlays */ ];
};
}The framework unifies the handling of packages, apps, and checks under a single "Scoped Package Tree" model.
- Unified Entry: Supports both single-file (
<name>.nix) and directory-based (<name>/package.nix) definitions. - Encapsulation: If a directory contains
package.nix, it is treated exclusively as a package definition. Other.nixfiles in that directory are ignored by the automatic scanner (treated as internal helper files). - Unified Build: All components are built using
callPackage, enjoying automatic dependency injection frompkgs. - Unified Scoping:
scope.nixis supported in all hierarchies (pkgs,apps,checks) to customize dependencies or inject parameters. - Explicit Context: The
scope.nixfunction receives the full system context (pkgs,self,inputs,system,lib) as arguments, allowing users to explicitly inject them into the package scope if desired. Auto-injection is avoided to keep the default scope clean.
- Apps: Automatically converts the built package into an App structure (
{ type="app"; program="..."; }) by inferring the main program (viameta.mainProgramor package name). - Checks: Treated as packages that run tests during build. Access to
selforinputsis available via function arguments.
The framework uses callPackage to build packages. You can customize the callPackage context (scope) via scope.nix.
- File:
<dir>/scope.nix(Applies to current directory and subdirectories) - Mechanism:
package.nixis built usingcurrentScope.callPackage.scope.nixmodifiescurrentScopefor its directory (and children).
- Signature:
{ pkgs, inputs, ... }: { scope = ...; args = ...; }- scope (Optional): The base package set (e.g.,
pkgs.pythonPackages) to use forcallPackage.- If provided: Replaces the parent scope.
- If omitted: Inherits the parent scope.
- args (Optional): Attributes to pass as the second argument to
callPackage.- These are merged with inherited args from parent directories.
- Useful for injecting dependencies or configuration into
package.nix.
- scope (Optional): The base package set (e.g.,
- Granularity: Works at both directory level (for groups of packages) and package level (sibling of
package.nix). - Usage: Essential for Python, Perl, and other language-specific package sets, or for injecting parameters into packages.
-
lib/: Core utility library with Haskell-inspired functional programming patterns
lib/flake-fhs.nix: Entry point wrapper formkFlakelib/fhs-core.nix: Core implementation (mkFlakeCore)lib/fhs-modules.nix: Module system logic and output generationlib/fhs-pkgs.nix: Package loading logiclib/fhs-lib.nix: Library preparation and recursive loaderlib/fhs-config.nix: Configuration optionslib/pkg-tools.nix: Package helper utilitieslib/dict.nix,lib/list.nix,lib/file.nix: Fundamental utilities
-
templates/: Project templates for different use cases
std: Standard template with complete nixos-config and flake outputs 1:1 namingshort: Short-named template with complete nixos-configzero: Minimal template with only flake.nix (directories left for user to create)project: Project-embedded template with./nixdirectory (for non-Nix projects)
The framework implements a module system with three mutually exclusive module types:
Module Types (Mutually Exclusive)
│
├─ Guarded Directory Module
│ ├─ Identifier: Directory contains options.nix
│ ├─ Features:
│ │ ├─ Auto-generates enable option
│ │ ├─ Config files wrapped with mkIf enable
│ │ └─ Nested modules check ALL parent enables
│ ├─ Constraints: Cannot have default.nix (conflict error)
│ └─ Use Case: Optional feature modules
│
├─ Traditional Directory Module
│ ├─ Identifier: Directory contains default.nix (no options.nix)
│ ├─ Features: Direct export, no enable mechanism
│ ├─ Constraints: No nesting (subdirs with default.nix are NOT recognized)
│ └─ Use Case: Configuration sets, complex modules
│
└─ Single File Module
├─ Identifier: Standalone file matching suffix (default: .nix)
├─ Features: Direct export, no enable mechanism
└─ Use Case: Simple modules
-
Guarded Modules: Directories with
options.nix- Auto-generates
enableoption if not manually defined - Config files (matching suffix, default
.nix) are recursively collected from:- Current directory
- All non-guarded subdirectories (regardless of
default.nixpresence)
- All collected config files are wrapped with
mkIf enable - Nested guarded modules check ALL parent enables (not just immediate parent)
- Conflict: Cannot have both
options.nixanddefault.nixin the same directory
- Auto-generates
-
Traditional Modules: Directories with
default.nix(nooptions.nix)- Directly exported, no enable mechanism
- No nesting: Subdirectories with
default.nixare NOT recognized as modules
-
Single File Modules: Standalone files matching the configured suffix (default:
.nix)- Directly exported, no enable mechanism
- Found recursively in all non-guarded, non-traditional directories
The file suffix for auto-discovering config files and single-file modules is configurable:
# flake.nix
flake-fhs.lib.mkFlake { inherit inputs; } {
layout.nixosModules = {
subdirs = [ "modules" ];
suffix = ".mod.nix"; # Custom suffix (default: ".nix")
};
}This allows keeping helper .nix files that shouldn't be auto-imported alongside config files.
-
Individual Module Outputs: Each module generates a single output:
nixosModules.<modPath>: The complete module (options + config)- Example:
modules/services/web-server/→nixosModules.services.web-server
-
Default Module Export:
nixosModules.defaultincludes ALL modules:- All guarded modules (with their enable guards)
- All traditional modules
- All single file modules
- Allows importing all modules with:
imports = [ flake.nixosModules.default ];
When guarded modules are nested, child modules' configs check ALL parent enables:
modules/
└── network/ # network.enable
├── options.nix
├── config.nix
└── services/
└── web/ # network.enable && network.services.web.enable
├── options.nix
└── config.nix
Options must strictly match the directory structure. The optionsMode configuration has been removed.
modules/foo/options.nixmust define options underoptions.foo.*modules/foo/bar/options.nixmust define options underoptions.foo.bar.*
- Use immutable data structures
- Prefer function composition (use tool functions from
lib/(i.e.self.lib) andbuiltinsandlib) - Implement higher-order functions for reusable operations and save general ones into
lib/ - Follow the utility patterns established in
lib/dict.nixandlib/list.nix - Always add Haskell-style type-signatures for reusable or complex functions
- Follow nixpkgs best practices for package definitions
- Use standard
pkgs/by-name/structure for packages (pkgs/<name>/package.nix) - Implement proper options and config separation in modules
- Leverage Nix's type system extensively
- Core (Nix):
checks/template-validation/default.niximplements a pure-Nix validator that mocks inputs to evaluate all templates against the current library code. It runs automatically vianix flake check. - Feature Tests: Standalone checks (e.g.,
checks/flake-option.nix,checks/scope.nix) validate specific library features by generating minimal flake structures in the Nix store. - Integration (Python):
checks/template-validation/validators.pysimulates real-world usage by creating temporary directories, replacing URLs, and running actual Nix commands. Use this for deep integration testing.
nix flake check # Standard test (CI friendly)
python checks/template-validation/validators.py # Manual integration test (Full simulation)Critical Understanding: Nix uses lazy evaluation, which has profound implications for test design.
Unreferenced let bindings are never evaluated:
let
checks = {
test1 = if someCondition then throw "FAIL" else true;
};
# ❌ WRONG: checks is never used, so test1 is never evaluated!
in
pkgs.runCommand "test" { } ''
echo "PASS" # Hardcoded success message
touch $out
''Tests must reference check results in the derivation to force evaluation:
let
checks = {
test1 = if someCondition then "FAIL: reason" else "PASS";
test2 = builtins.length someList == 2 || "FAIL: expected 2 items";
};
checkResults = builtins.attrValues checks;
in
pkgs.runCommand "test" { } ''
# Output actual check results (forces evaluation)
${builtins.concatStringsSep "\n" (map (r: "echo '${r}'") checkResults)}
# Fail if any check failed
if echo '${builtins.toJSON checks}' | grep -q FAIL; then
exit 1
fi
touch $out
''- Never hardcode test output - Tests should output actual check results
- Force evaluation via derivation - Reference check results in
runCommandto ensure they're evaluated
Typical flake.nix for users (showing common options):
{
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
flake-fhs.url = "github:luochen1990/flake-fhs";
};
outputs = inputs@{ flake-fhs, ... }:
flake-fhs.lib.mkFlake { inherit inputs; } {
# Optional: Explicitly specify systems (flake-parts style)
systems = [ "x86_64-linux" "x86_64-darwin" ];
# Optional: Nixpkgs configuration
nixpkgs.config = {
allowUnfree = true;
};
# Optional: Source roots
# layout.roots = [ "" "/nix" ];
# Optional: Enable Colmena integration
# colmena.enable = true;
};
}The framework provides native support for Colmena, a deployment tool for NixOS.
To enable Colmena support, set colmena.enable = true in your mkFlake configuration. This will generate a colmenaHive output that can be used directly by Colmena.
outputs = inputs@{ flake-fhs, ... }:
flake-fhs.lib.mkFlake { inherit inputs; } {
# ...
colmena.enable = true;
};- Automatically discovers nodes from
nixosConfigurationsdirectory structure. - Injects
profileNameinto module arguments for each node (accessible viaconfig.profileName). - Sets
deployment.allowLocalDeployment = trueby default. - Inherits
nixpkgsrevision info from the flake inputs.
The mkFlake function has been redesigned to use Nix's module system (lib.evalModules):
- First parameter: Context including
inputs,self,nixpkgs,lib - Second parameter: Configuration module with type-safe options
- Core implementation:
mkFlakeCore(inlib/fhs-core.nix) contains the actual flake generation logic - Configuration options: Defined in
flakeFhsOptions(inlib/fhs-config.nix) with full type checking
- Pattern over Enumeration: Describe structures using patterns (e.g.,
manual-*.md) instead of exhaustive lists to reduce maintenance burden and noise. - Mechanism Focus: Explain shared mechanisms (e.g., "Scoped Package Tree") to guide logical consistency across related components.
- Conciseness: Keep instructions high-level and directive. Avoid redundancy with the actual documentation content.
- SSOT & DRY: Central
mkFlakefunction handles all output generation - Convention Over Configuration: Standardized directory structure eliminates boilerplate
- Performance: Partial loading mechanism for large module sets
- Type Safety: Leverages Nix's type system extensively
- Core logic split across
lib/fhs-*.nixfiles (core,modules,pkgs,config,lib) - Entry point in
lib/flake-fhs.nix - Shared utilities in
lib/directory - Templates in
templates/with embedded documentation - Comprehensive manual in
docs/manual.md
- Utility Functions: Reuse existing utilities from
lib/directory - Module System: Maintain guarded/unguarded module loading behavior
- Template Updates: Ensure templates work with current
mkFlakeimplementation - Testing: Run template validation after changes that affect flake outputs
- Documentation: Update
docs/manual.mdand related split documents (manual-*.md) when features change. Ensure the "Scoped Package Tree" concept is consistent acrosspkgs,apps,checksdocumentation.
The manual is modularized (docs/manual-*.md) with docs/manual.md as the entry point.
- Core Reference:
docs/manual-pkgs.mddefines the "Scoped Package Tree" model used bypkgs,apps,shells, andchecks. - Maintenance:
- Update
manual.mdfor high-level directory mapping changes. - Update
manual-pkgs.mdfor shared build/scope mechanism changes. - Update specific
manual-*.mdfiles for feature-specific changes.
- Update